chore: Also format scripts and skills.

This commit is contained in:
cpojer
2026-01-31 21:21:09 +09:00
parent a767c584c7
commit 76b5208b11
95 changed files with 2250 additions and 1239 deletions

View File

@@ -8,8 +8,6 @@
"node_modules/", "node_modules/",
"patches/", "patches/",
"pnpm-lock.yaml/", "pnpm-lock.yaml/",
"scripts/",
"skills/",
"Swabble/", "Swabble/",
"vendor/", "vendor/",
], ],

View File

@@ -1,7 +1,7 @@
--- ---
name: prose name: prose
description: OpenProse VM skill pack. Activate on any `prose` command, .prose files, or OpenProse mentions; orchestrates multi-agent workflows. description: OpenProse VM skill pack. Activate on any `prose` command, .prose files, or OpenProse mentions; orchestrates multi-agent workflows.
metadata: {"openclaw":{"emoji":"🪶","homepage":"https://www.prose.md"}} metadata: { "openclaw": { "emoji": "🪶", "homepage": "https://www.prose.md" } }
--- ---
# OpenProse Skill # OpenProse Skill
@@ -29,15 +29,15 @@ Activate this skill when the user:
When a user invokes `prose <command>`, intelligently route based on intent: When a user invokes `prose <command>`, intelligently route based on intent:
| Command | Action | | Command | Action |
|---------|--------| | ----------------------- | ------------------------------------------------------------- |
| `prose help` | Load `help.md`, guide user to what they need | | `prose help` | Load `help.md`, guide user to what they need |
| `prose run <file>` | Load VM (`prose.md` + state backend), execute the program | | `prose run <file>` | Load VM (`prose.md` + state backend), execute the program |
| `prose run handle/slug` | Fetch from registry, then execute (see Remote Programs below) | | `prose run handle/slug` | Fetch from registry, then execute (see Remote Programs below) |
| `prose compile <file>` | Load `compiler.md`, validate the program | | `prose compile <file>` | Load `compiler.md`, validate the program |
| `prose update` | Run migration (see Migration section below) | | `prose update` | Run migration (see Migration section below) |
| `prose examples` | Show or run example programs from `examples/` | | `prose examples` | Show or run example programs from `examples/` |
| Other | Intelligently interpret based on context | | Other | Intelligently interpret based on context |
### Important: Single Skill ### Important: Single Skill
@@ -77,11 +77,11 @@ prose run alice/code-review
**Resolution rules:** **Resolution rules:**
| Input | Resolution | | Input | Resolution |
|-------|------------| | ----------------------------------- | -------------------------------------- |
| Starts with `http://` or `https://` | Fetch directly from URL | | Starts with `http://` or `https://` | Fetch directly from URL |
| Contains `/` but no protocol | Resolve to `https://p.prose.md/{path}` | | Contains `/` but no protocol | Resolve to `https://p.prose.md/{path}` |
| Otherwise | Treat as local file path | | Otherwise | Treat as local file path |
**Steps for remote programs:** **Steps for remote programs:**
@@ -102,33 +102,33 @@ use "alice/research" as research # Registry shorthand
**Do NOT search for OpenProse documentation files.** All skill files are co-located with this SKILL.md file: **Do NOT search for OpenProse documentation files.** All skill files are co-located with this SKILL.md file:
| File | Location | Purpose | | File | Location | Purpose |
| ------------------------- | --------------------------- | ----------------------------------------- | | -------------------------- | --------------------------- | ---------------------------------------------- |
| `prose.md` | Same directory as this file | VM semantics (load to run programs) | | `prose.md` | Same directory as this file | VM semantics (load to run programs) |
| `help.md` | Same directory as this file | Help, FAQs, onboarding (load for `prose help`) | | `help.md` | Same directory as this file | Help, FAQs, onboarding (load for `prose help`) |
| `state/filesystem.md` | Same directory as this file | File-based state (default, load with VM) | | `state/filesystem.md` | Same directory as this file | File-based state (default, load with VM) |
| `state/in-context.md` | Same directory as this file | In-context state (on request) | | `state/in-context.md` | Same directory as this file | In-context state (on request) |
| `state/sqlite.md` | Same directory as this file | SQLite state (experimental, on request) | | `state/sqlite.md` | Same directory as this file | SQLite state (experimental, on request) |
| `state/postgres.md` | Same directory as this file | PostgreSQL state (experimental, on request) | | `state/postgres.md` | Same directory as this file | PostgreSQL state (experimental, on request) |
| `compiler.md` | Same directory as this file | Compiler/validator (load only on request) | | `compiler.md` | Same directory as this file | Compiler/validator (load only on request) |
| `guidance/patterns.md` | Same directory as this file | Best practices (load when writing .prose) | | `guidance/patterns.md` | Same directory as this file | Best practices (load when writing .prose) |
| `guidance/antipatterns.md`| Same directory as this file | What to avoid (load when writing .prose) | | `guidance/antipatterns.md` | Same directory as this file | What to avoid (load when writing .prose) |
| `examples/` | Same directory as this file | 37 example programs | | `examples/` | Same directory as this file | 37 example programs |
**User workspace files** (these ARE in the user's project): **User workspace files** (these ARE in the user's project):
| File/Directory | Location | Purpose | | File/Directory | Location | Purpose |
| ---------------- | ------------------------ | ----------------------------------- | | ---------------- | ------------------------ | --------------------------------- |
| `.prose/.env` | User's working directory | Config (key=value format) | | `.prose/.env` | User's working directory | Config (key=value format) |
| `.prose/runs/` | User's working directory | Runtime state for file-based mode | | `.prose/runs/` | User's working directory | Runtime state for file-based mode |
| `.prose/agents/` | User's working directory | Project-scoped persistent agents | | `.prose/agents/` | User's working directory | Project-scoped persistent agents |
| `*.prose` files | User's project | User-created programs to execute | | `*.prose` files | User's project | User-created programs to execute |
**User-level files** (in user's home directory, shared across all projects): **User-level files** (in user's home directory, shared across all projects):
| File/Directory | Location | Purpose | | File/Directory | Location | Purpose |
| ----------------- | ---------------- | ---------------------------------------- | | ------------------ | --------------- | --------------------------------------------- |
| `~/.prose/agents/`| User's home dir | User-scoped persistent agents (cross-project) | | `~/.prose/agents/` | User's home dir | User-scoped persistent agents (cross-project) |
When you need to read `prose.md` or `compiler.md`, read them from the same directory where you found this SKILL.md file. Never search the user's workspace for these files. When you need to read `prose.md` or `compiler.md`, read them from the same directory where you found this SKILL.md file. Never search the user's workspace for these files.
@@ -136,20 +136,21 @@ When you need to read `prose.md` or `compiler.md`, read them from the same direc
## Core Documentation ## Core Documentation
| File | Purpose | When to Load | | File | Purpose | When to Load |
| --------------------- | -------------------- | ---------------------------------------------- | | -------------------------- | ------------------------------- | --------------------------------------------------------------------- |
| `prose.md` | VM / Interpreter | Always load to run programs | | `prose.md` | VM / Interpreter | Always load to run programs |
| `state/filesystem.md` | File-based state | Load with VM (default) | | `state/filesystem.md` | File-based state | Load with VM (default) |
| `state/in-context.md` | In-context state | Only if user requests `--in-context` or says "use in-context state" | | `state/in-context.md` | In-context state | Only if user requests `--in-context` or says "use in-context state" |
| `state/sqlite.md` | SQLite state (experimental) | Only if user requests `--state=sqlite` (requires sqlite3 CLI) | | `state/sqlite.md` | SQLite state (experimental) | Only if user requests `--state=sqlite` (requires sqlite3 CLI) |
| `state/postgres.md` | PostgreSQL state (experimental) | Only if user requests `--state=postgres` (requires psql + PostgreSQL) | | `state/postgres.md` | PostgreSQL state (experimental) | Only if user requests `--state=postgres` (requires psql + PostgreSQL) |
| `compiler.md` | Compiler / Validator | **Only** when user asks to compile or validate | | `compiler.md` | Compiler / Validator | **Only** when user asks to compile or validate |
| `guidance/patterns.md` | Best practices | Load when **writing** new .prose files | | `guidance/patterns.md` | Best practices | Load when **writing** new .prose files |
| `guidance/antipatterns.md` | What to avoid | Load when **writing** new .prose files | | `guidance/antipatterns.md` | What to avoid | Load when **writing** new .prose files |
### Authoring Guidance ### Authoring Guidance
When the user asks you to **write or create** a new `.prose` file, load the guidance files: When the user asks you to **write or create** a new `.prose` file, load the guidance files:
- `guidance/patterns.md` — Proven patterns for robust, efficient programs - `guidance/patterns.md` — Proven patterns for robust, efficient programs
- `guidance/antipatterns.md` — Common mistakes to avoid - `guidance/antipatterns.md` — Common mistakes to avoid
@@ -159,12 +160,12 @@ Do **not** load these when running or compiling—they're for authoring only.
OpenProse supports three state management approaches: OpenProse supports three state management approaches:
| Mode | When to Use | State Location | | Mode | When to Use | State Location |
|------|-------------|----------------| | --------------------------- | ----------------------------------------------------------------- | --------------------------- |
| **filesystem** (default) | Complex programs, resumption needed, debugging | `.prose/runs/{id}/` files | | **filesystem** (default) | Complex programs, resumption needed, debugging | `.prose/runs/{id}/` files |
| **in-context** | Simple programs (<30 statements), no persistence needed | Conversation history | | **in-context** | Simple programs (<30 statements), no persistence needed | Conversation history |
| **sqlite** (experimental) | Queryable state, atomic transactions, flexible schema | `.prose/runs/{id}/state.db` | | **sqlite** (experimental) | Queryable state, atomic transactions, flexible schema | `.prose/runs/{id}/state.db` |
| **postgres** (experimental) | True concurrent writes, external integrations, team collaboration | PostgreSQL database | | **postgres** (experimental) | True concurrent writes, external integrations, team collaboration | PostgreSQL database |
**Default behavior:** When loading `prose.md`, also load `state/filesystem.md`. This is the recommended mode for most programs. **Default behavior:** When loading `prose.md`, also load `state/filesystem.md`. This is the recommended mode for most programs.
@@ -177,6 +178,7 @@ OpenProse supports three state management approaches:
**⚠️ Security Note:** Database credentials in `OPENPROSE_POSTGRES_URL` are passed to subagent sessions and visible in logs. Advise users to use a dedicated database with limited-privilege credentials. See `state/postgres.md` for secure setup guidance. **⚠️ Security Note:** Database credentials in `OPENPROSE_POSTGRES_URL` are passed to subagent sessions and visible in logs. Advise users to use a dedicated database with limited-privilege credentials. See `state/postgres.md` for secure setup guidance.
1. **Check for connection configuration first:** 1. **Check for connection configuration first:**
```bash ```bash
# Check .prose/.env for OPENPROSE_POSTGRES_URL # Check .prose/.env for OPENPROSE_POSTGRES_URL
cat .prose/.env 2>/dev/null | grep OPENPROSE_POSTGRES_URL cat .prose/.env 2>/dev/null | grep OPENPROSE_POSTGRES_URL
@@ -185,11 +187,13 @@ OpenProse supports three state management approaches:
``` ```
2. **If connection string exists, verify connectivity:** 2. **If connection string exists, verify connectivity:**
```bash ```bash
psql "$OPENPROSE_POSTGRES_URL" -c "SELECT 1" 2>&1 psql "$OPENPROSE_POSTGRES_URL" -c "SELECT 1" 2>&1
``` ```
3. **If not configured or connection fails, advise the user:** 3. **If not configured or connection fails, advise the user:**
``` ```
⚠️ PostgreSQL state requires a connection URL. ⚠️ PostgreSQL state requires a connection URL.
@@ -261,10 +265,10 @@ When a user invokes `prose update`, check for legacy file structures and migrate
### Legacy Paths to Check ### Legacy Paths to Check
| Legacy Path | Current Path | Notes | | Legacy Path | Current Path | Notes |
|-------------|--------------|-------| | ------------------- | -------------- | -------------------------------- |
| `.prose/state.json` | `.prose/.env` | Convert JSON to key=value format | | `.prose/state.json` | `.prose/.env` | Convert JSON to key=value format |
| `.prose/execution/` | `.prose/runs/` | Rename directory | | `.prose/execution/` | `.prose/runs/` | Rename directory |
### Migration Steps ### Migration Steps
@@ -272,7 +276,7 @@ When a user invokes `prose update`, check for legacy file structures and migrate
- If exists, read the JSON content - If exists, read the JSON content
- Convert to `.env` format: - Convert to `.env` format:
```json ```json
{"OPENPROSE_TELEMETRY": "enabled", "USER_ID": "user-xxx", "SESSION_ID": "sess-xxx"} { "OPENPROSE_TELEMETRY": "enabled", "USER_ID": "user-xxx", "SESSION_ID": "sess-xxx" }
``` ```
becomes: becomes:
```env ```env
@@ -301,6 +305,7 @@ When a user invokes `prose update`, check for legacy file structures and migrate
``` ```
If no legacy files are found: If no legacy files are found:
``` ```
✅ Workspace already up to date. No migration needed. ✅ Workspace already up to date. No migration needed.
``` ```
@@ -309,10 +314,10 @@ If no legacy files are found:
These documentation files were renamed in the skill itself (not user workspace): These documentation files were renamed in the skill itself (not user workspace):
| Legacy Name | Current Name | | Legacy Name | Current Name |
|-------------|--------------| | ----------------- | -------------------------- |
| `docs.md` | `compiler.md` | | `docs.md` | `compiler.md` |
| `patterns.md` | `guidance/patterns.md` | | `patterns.md` | `guidance/patterns.md` |
| `antipatterns.md` | `guidance/antipatterns.md` | | `antipatterns.md` | `guidance/antipatterns.md` |
If you encounter references to the old names in user prompts or external docs, map them to the current paths. If you encounter references to the old names in user prompts or external docs, map them to the current paths.

View File

@@ -28,55 +28,55 @@ An alternative register for OpenProse that draws from One Thousand and One Night
### Core Constructs ### Core Constructs
| Functional | Nights | Reference | | Functional | Nights | Reference |
|------------|--------|-----------| | ---------- | -------- | ------------------------------------- |
| `agent` | `djinn` | Spirit bound to serve, grants wishes | | `agent` | `djinn` | Spirit bound to serve, grants wishes |
| `session` | `tale` | A story told, a narrative unit | | `session` | `tale` | A story told, a narrative unit |
| `parallel` | `bazaar` | Many voices, many stalls, all at once | | `parallel` | `bazaar` | Many voices, many stalls, all at once |
| `block` | `frame` | A story that contains other stories | | `block` | `frame` | A story that contains other stories |
### Composition & Binding ### Composition & Binding
| Functional | Nights | Reference | | Functional | Nights | Reference |
|------------|--------|-----------| | ---------- | --------- | -------------------------------- |
| `use` | `conjure` | Summoning from elsewhere | | `use` | `conjure` | Summoning from elsewhere |
| `input` | `wish` | What is asked of the djinn | | `input` | `wish` | What is asked of the djinn |
| `output` | `gift` | What is granted in return | | `output` | `gift` | What is granted in return |
| `let` | `name` | Naming has power (same as folk) | | `let` | `name` | Naming has power (same as folk) |
| `const` | `oath` | Unbreakable vow, sealed | | `const` | `oath` | Unbreakable vow, sealed |
| `context` | `scroll` | What is written and passed along | | `context` | `scroll` | What is written and passed along |
### Control Flow ### Control Flow
| Functional | Nights | Reference | | Functional | Nights | Reference |
|------------|--------|-----------| | ---------- | ------------------ | ------------------------------------ |
| `repeat N` | `N nights` | "For a thousand and one nights..." | | `repeat N` | `N nights` | "For a thousand and one nights..." |
| `for...in` | `for each...among` | Among the merchants, among the tales | | `for...in` | `for each...among` | Among the merchants, among the tales |
| `loop` | `telling` | The telling continues | | `loop` | `telling` | The telling continues |
| `until` | `until` | Unchanged | | `until` | `until` | Unchanged |
| `while` | `while` | Unchanged | | `while` | `while` | Unchanged |
| `choice` | `crossroads` | Where the story forks | | `choice` | `crossroads` | Where the story forks |
| `option` | `path` | One way the story could go | | `option` | `path` | One way the story could go |
| `if` | `should` | Narrative conditional | | `if` | `should` | Narrative conditional |
| `elif` | `or should` | Continued conditional | | `elif` | `or should` | Continued conditional |
| `else` | `otherwise` | The other telling | | `else` | `otherwise` | The other telling |
### Error Handling ### Error Handling
| Functional | Nights | Reference | | Functional | Nights | Reference |
|------------|--------|-----------| | ---------- | -------------------------- | -------------------------- |
| `try` | `venture` | Setting out on the journey | | `try` | `venture` | Setting out on the journey |
| `catch` | `should misfortune strike` | The tale turns dark | | `catch` | `should misfortune strike` | The tale turns dark |
| `finally` | `and so it was` | The inevitable ending | | `finally` | `and so it was` | The inevitable ending |
| `throw` | `curse` | Ill fate pronounced | | `throw` | `curse` | Ill fate pronounced |
| `retry` | `persist` | The hero tries again | | `retry` | `persist` | The hero tries again |
### Session Properties ### Session Properties
| Functional | Nights | Reference | | Functional | Nights | Reference |
|------------|--------|-----------| | ---------- | --------- | ------------------------------ |
| `prompt` | `command` | What is commanded of the djinn | | `prompt` | `command` | What is commanded of the djinn |
| `model` | `spirit` | Which spirit answers | | `model` | `spirit` | Which spirit answers |
### Unchanged ### Unchanged
@@ -297,14 +297,14 @@ oath config = { spirit: "opus", persist: 3 }
## Key Arabian Nights Concepts ## Key Arabian Nights Concepts
| Term | Meaning | Used for | | Term | Meaning | Used for |
|------|---------|----------| | ------------ | --------------------------------------- | --------------------- |
| Scheherazade | The narrator who tells tales to survive | (the program author) | | Scheherazade | The narrator who tells tales to survive | (the program author) |
| Djinn | Supernatural spirit, bound to serve | `agent``djinn` | | Djinn | Supernatural spirit, bound to serve | `agent``djinn` |
| Frame story | A story that contains other stories | `block``frame` | | Frame story | A story that contains other stories | `block``frame` |
| Wish | What is asked of the djinn | `input``wish` | | Wish | What is asked of the djinn | `input``wish` |
| Oath | Unbreakable promise | `const``oath` | | Oath | Unbreakable promise | `const``oath` |
| Bazaar | Marketplace, many vendors | `parallel``bazaar` | | Bazaar | Marketplace, many vendors | `parallel``bazaar` |
--- ---
@@ -312,36 +312,36 @@ oath config = { spirit: "opus", persist: 3 }
### For `djinn` (agent) ### For `djinn` (agent)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ---------- | ---------------------------------- |
| `genie` | Disney connotation, less literary | | `genie` | Disney connotation, less literary |
| `spirit` | Used for `model` | | `spirit` | Used for `model` |
| `ifrit` | Too specific (a type of djinn) | | `ifrit` | Too specific (a type of djinn) |
| `narrator` | Too meta, Scheherazade is the user | | `narrator` | Too meta, Scheherazade is the user |
### For `tale` (session) ### For `tale` (session)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | ----------------------------------- |
| `story` | Good but `tale` feels more literary | | `story` | Good but `tale` feels more literary |
| `night` | Reserved for `repeat N nights` | | `night` | Reserved for `repeat N nights` |
| `chapter` | More Western/novelistic | | `chapter` | More Western/novelistic |
### For `bazaar` (parallel) ### For `bazaar` (parallel)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | ------------------------------------------ |
| `caravan` | Sequential connotation (one after another) | | `caravan` | Sequential connotation (one after another) |
| `chorus` | Greek, wrong tradition | | `chorus` | Greek, wrong tradition |
| `souk` | Less widely known | | `souk` | Less widely known |
### For `scroll` (context) ### For `scroll` (context)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | ------------------ |
| `letter` | Too small/personal | | `letter` | Too small/personal |
| `tome` | Too large | | `tome` | Too large |
| `message` | Too plain | | `message` | Too plain |
--- ---

View File

@@ -29,55 +29,55 @@ An alternative register for OpenProse that draws from the works of Jorge Luis Bo
### Core Constructs ### Core Constructs
| Functional | Borges | Reference | | Functional | Borges | Reference |
|------------|--------|-----------| | ---------- | --------- | --------------------------------------------------------------- |
| `agent` | `dreamer` | "The Circular Ruins" — dreamers who dream worlds into existence | | `agent` | `dreamer` | "The Circular Ruins" — dreamers who dream worlds into existence |
| `session` | `dream` | Each execution is a dream within the dreamer | | `session` | `dream` | Each execution is a dream within the dreamer |
| `parallel` | `forking` | "The Garden of Forking Paths" — branching timelines | | `parallel` | `forking` | "The Garden of Forking Paths" — branching timelines |
| `block` | `chapter` | Books within books, self-referential structure | | `block` | `chapter` | Books within books, self-referential structure |
### Composition & Binding ### Composition & Binding
| Functional | Borges | Reference | | Functional | Borges | Reference |
|------------|--------|-----------| | ---------- | ---------- | -------------------------------------------------------- |
| `use` | `retrieve` | "The Library of Babel" — retrieving from infinite stacks | | `use` | `retrieve` | "The Library of Babel" — retrieving from infinite stacks |
| `input` | `axiom` | The given premise (Borges' scholarly/mathematical tone) | | `input` | `axiom` | The given premise (Borges' scholarly/mathematical tone) |
| `output` | `theorem` | What is derived from the axioms | | `output` | `theorem` | What is derived from the axioms |
| `let` | `inscribe` | Writing something into being | | `let` | `inscribe` | Writing something into being |
| `const` | `zahir` | "The Zahir" — unforgettable, unchangeable, fixed in mind | | `const` | `zahir` | "The Zahir" — unforgettable, unchangeable, fixed in mind |
| `context` | `memory` | "Funes the Memorious" — perfect, total recall | | `context` | `memory` | "Funes the Memorious" — perfect, total recall |
### Control Flow ### Control Flow
| Functional | Borges | Reference | | Functional | Borges | Reference |
|------------|--------|-----------| | ---------- | ------------------- | -------------------------------------- |
| `repeat N` | `N mirrors` | Infinite reflections facing each other | | `repeat N` | `N mirrors` | Infinite reflections facing each other |
| `for...in` | `for each...within` | Slightly more Borgesian preposition | | `for...in` | `for each...within` | Slightly more Borgesian preposition |
| `loop` | `labyrinth` | The maze that folds back on itself | | `loop` | `labyrinth` | The maze that folds back on itself |
| `until` | `until` | Unchanged | | `until` | `until` | Unchanged |
| `while` | `while` | Unchanged | | `while` | `while` | Unchanged |
| `choice` | `bifurcation` | The forking of paths | | `choice` | `bifurcation` | The forking of paths |
| `option` | `branch` | One branch of diverging time | | `option` | `branch` | One branch of diverging time |
| `if` | `should` | Scholarly conditional | | `if` | `should` | Scholarly conditional |
| `elif` | `or should` | Continued conditional | | `elif` | `or should` | Continued conditional |
| `else` | `otherwise` | Natural alternative | | `else` | `otherwise` | Natural alternative |
### Error Handling ### Error Handling
| Functional | Borges | Reference | | Functional | Borges | Reference |
|------------|--------|-----------| | ---------- | ------------ | -------------------------------------- |
| `try` | `venture` | Entering the labyrinth | | `try` | `venture` | Entering the labyrinth |
| `catch` | `lest` | "Lest it fail..." (archaic, scholarly) | | `catch` | `lest` | "Lest it fail..." (archaic, scholarly) |
| `finally` | `ultimately` | The inevitable conclusion | | `finally` | `ultimately` | The inevitable conclusion |
| `throw` | `shatter` | Breaking the mirror, ending the dream | | `throw` | `shatter` | Breaking the mirror, ending the dream |
| `retry` | `recur` | Infinite regress, trying again | | `retry` | `recur` | Infinite regress, trying again |
### Session Properties ### Session Properties
| Functional | Borges | Reference | | Functional | Borges | Reference |
|------------|--------|-----------| | ---------- | -------- | ------------------------------ |
| `prompt` | `query` | Asking the Library | | `prompt` | `query` | Asking the Library |
| `model` | `author` | Which author writes this dream | | `model` | `author` | Which author writes this dream |
### Unchanged ### Unchanged
@@ -299,15 +299,15 @@ zahir config = { author: "opus", recur: 3 }
For those unfamiliar with the source material: For those unfamiliar with the source material:
| Work | Concept Used | Summary | | Work | Concept Used | Summary |
|------|--------------|---------| | ----------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------- |
| "The Circular Ruins" | `dreamer`, `dream` | A man dreams another man into existence, only to discover he himself is being dreamed | | "The Circular Ruins" | `dreamer`, `dream` | A man dreams another man into existence, only to discover he himself is being dreamed |
| "The Garden of Forking Paths" | `forking`, `bifurcation`, `branch` | A labyrinth that is a book; time forks perpetually into diverging futures | | "The Garden of Forking Paths" | `forking`, `bifurcation`, `branch` | A labyrinth that is a book; time forks perpetually into diverging futures |
| "The Library of Babel" | `retrieve` | An infinite library containing every possible book | | "The Library of Babel" | `retrieve` | An infinite library containing every possible book |
| "Funes the Memorious" | `memory` | A man with perfect memory who cannot forget anything | | "Funes the Memorious" | `memory` | A man with perfect memory who cannot forget anything |
| "The Zahir" | `zahir` | An object that, once seen, cannot be forgotten or ignored | | "The Zahir" | `zahir` | An object that, once seen, cannot be forgotten or ignored |
| "The Aleph" | (not used) | A point in space containing all other points | | "The Aleph" | (not used) | A point in space containing all other points |
| "Tlön, Uqbar, Orbis Tertius" | (not used) | A fictional world that gradually becomes real | | "Tlön, Uqbar, Orbis Tertius" | (not used) | A fictional world that gradually becomes real |
--- ---
@@ -315,35 +315,35 @@ For those unfamiliar with the source material:
### For `dreamer` (agent) ### For `dreamer` (agent)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ----------- | ------------------------- |
| `author` | Used for `model` instead | | `author` | Used for `model` instead |
| `scribe` | Too passive, just records | | `scribe` | Too passive, just records |
| `librarian` | More curator than creator | | `librarian` | More curator than creator |
### For `labyrinth` (loop) ### For `labyrinth` (loop)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ---------------- | ---------------- |
| `recursion` | Too technical | | `recursion` | Too technical |
| `eternal return` | Too long | | `eternal return` | Too long |
| `ouroboros` | Wrong mythology | | `ouroboros` | Wrong mythology |
### For `zahir` (const) ### For `zahir` (const)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | --------------------------------------------- |
| `aleph` | The Aleph is about totality, not immutability | | `aleph` | The Aleph is about totality, not immutability |
| `fixed` | Too plain | | `fixed` | Too plain |
| `eternal` | Overused | | `eternal` | Overused |
### For `memory` (context) ### For `memory` (context)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | --------------------------------- |
| `funes` | Too obscure as standalone keyword | | `funes` | Too obscure as standalone keyword |
| `recall` | Sounds like a function call | | `recall` | Sounds like a function call |
| `archive` | More Library of Babel than Funes | | `archive` | More Library of Babel than Funes |
--- ---

View File

@@ -29,55 +29,55 @@ An alternative register for OpenProse that leans into literary, theatrical, and
### Core Constructs ### Core Constructs
| Functional | Folk | Origin | Connotation | | Functional | Folk | Origin | Connotation |
|------------|------|--------|-------------| | ---------- | ---------- | -------- | -------------------------------------- |
| `agent` | `sprite` | Folklore | Quick, light, ephemeral spirit helper | | `agent` | `sprite` | Folklore | Quick, light, ephemeral spirit helper |
| `session` | `scene` | Theatre | A moment of action, theatrical framing | | `session` | `scene` | Theatre | A moment of action, theatrical framing |
| `parallel` | `ensemble` | Theatre | Everyone performs together | | `parallel` | `ensemble` | Theatre | Everyone performs together |
| `block` | `act` | Theatre | Reusable unit of dramatic action | | `block` | `act` | Theatre | Reusable unit of dramatic action |
### Composition & Binding ### Composition & Binding
| Functional | Folk | Origin | Connotation | | Functional | Folk | Origin | Connotation |
|------------|------|--------|-------------| | ---------- | --------- | ----------------- | -------------------------------- |
| `use` | `summon` | Folklore | Calling forth from elsewhere | | `use` | `summon` | Folklore | Calling forth from elsewhere |
| `input` | `given` | Fairy tale | "Given a magic sword..." | | `input` | `given` | Fairy tale | "Given a magic sword..." |
| `output` | `yield` | Agriculture/magic | What the spell produces | | `output` | `yield` | Agriculture/magic | What the spell produces |
| `let` | `name` | Folklore | Naming has power (true names) | | `let` | `name` | Folklore | Naming has power (true names) |
| `const` | `seal` | Medieval | Unchangeable, wax seal on decree | | `const` | `seal` | Medieval | Unchangeable, wax seal on decree |
| `context` | `bearing` | Heraldry | What the messenger carries | | `context` | `bearing` | Heraldry | What the messenger carries |
### Control Flow ### Control Flow
| Functional | Folk | Origin | Connotation | | Functional | Folk | Origin | Connotation |
|------------|------|--------|-------------| | ---------- | ------------------ | ------------ | ----------------------------------- |
| `repeat N` | `N times` | Fairy tale | "Three times she called..." | | `repeat N` | `N times` | Fairy tale | "Three times she called..." |
| `for...in` | `for each...among` | Narrative | Slightly more storytelling | | `for...in` | `for each...among` | Narrative | Slightly more storytelling |
| `loop` | `loop` | — | Already poetic, unchanged | | `loop` | `loop` | — | Already poetic, unchanged |
| `until` | `until` | — | Already works, unchanged | | `until` | `until` | — | Already works, unchanged |
| `while` | `while` | — | Already works, unchanged | | `while` | `while` | — | Already works, unchanged |
| `choice` | `crossroads` | Folklore | Fateful decisions at the crossroads | | `choice` | `crossroads` | Folklore | Fateful decisions at the crossroads |
| `option` | `path` | Journey | Which path to take | | `option` | `path` | Journey | Which path to take |
| `if` | `when` | Narrative | "When the moon rises..." | | `if` | `when` | Narrative | "When the moon rises..." |
| `elif` | `or when` | Narrative | Continued conditional | | `elif` | `or when` | Narrative | Continued conditional |
| `else` | `otherwise` | Storytelling | Natural narrative alternative | | `else` | `otherwise` | Storytelling | Natural narrative alternative |
### Error Handling ### Error Handling
| Functional | Folk | Origin | Connotation | | Functional | Folk | Origin | Connotation |
|------------|------|--------|-------------| | ---------- | ---------------- | ---------- | ------------------------------ |
| `try` | `venture` | Adventure | Attempting something uncertain | | `try` | `venture` | Adventure | Attempting something uncertain |
| `catch` | `should it fail` | Narrative | Conditional failure handling | | `catch` | `should it fail` | Narrative | Conditional failure handling |
| `finally` | `ever after` | Fairy tale | "And ever after..." | | `finally` | `ever after` | Fairy tale | "And ever after..." |
| `throw` | `cry` | Drama | Raising alarm, calling out | | `throw` | `cry` | Drama | Raising alarm, calling out |
| `retry` | `persist` | Quest | Keep trying against odds | | `retry` | `persist` | Quest | Keep trying against odds |
### Session Properties ### Session Properties
| Functional | Folk | Origin | Connotation | | Functional | Folk | Origin | Connotation |
|------------|------|--------|-------------| | ---------- | -------- | -------- | ---------------------- |
| `prompt` | `charge` | Chivalry | Giving a quest or duty | | `prompt` | `charge` | Chivalry | Giving a quest or duty |
| `model` | `voice` | Theatre | Which voice speaks | | `model` | `voice` | Theatre | Which voice speaks |
### Unchanged ### Unchanged
@@ -274,39 +274,39 @@ perform review("quantum computing")
### For `sprite` (ephemeral agent) ### For `sprite` (ephemeral agent)
| Keyword | Origin | Rejected because | | Keyword | Origin | Rejected because |
|---------|--------|------------------| | --------- | ------- | ----------------------------------------- |
| `spark` | English | Good but less folklore | | `spark` | English | Good but less folklore |
| `wisp` | English | Too insubstantial | | `wisp` | English | Too insubstantial |
| `herald` | English | More messenger than worker | | `herald` | English | More messenger than worker |
| `courier` | French | Good functional alternative, not literary | | `courier` | French | Good functional alternative, not literary |
| `envoy` | French | Formal, diplomatic | | `envoy` | French | Formal, diplomatic |
### For `shade` (persistent agent, if implemented) ### For `shade` (persistent agent, if implemented)
| Keyword | Origin | Rejected because | | Keyword | Origin | Rejected because |
|---------|--------|------------------| | --------- | ---------- | --------------------------------- |
| `daemon` | Greek/Unix | Unix "always running" connotation | | `daemon` | Greek/Unix | Unix "always running" connotation |
| `oracle` | Greek | Too "read-only" feeling | | `oracle` | Greek | Too "read-only" feeling |
| `spirit` | Latin | Too close to `sprite` | | `spirit` | Latin | Too close to `sprite` |
| `specter` | Latin | Negative/spooky connotation | | `specter` | Latin | Negative/spooky connotation |
| `genius` | Roman | Overloaded (smart person) | | `genius` | Roman | Overloaded (smart person) |
### For `ensemble` (parallel) ### For `ensemble` (parallel)
| Keyword | Origin | Rejected because | | Keyword | Origin | Rejected because |
|---------|--------|------------------| | --------- | ------- | ----------------------------------------- |
| `chorus` | Greek | Everyone speaks same thing, not different | | `chorus` | Greek | Everyone speaks same thing, not different |
| `troupe` | French | Good alternative, slightly less clear | | `troupe` | French | Good alternative, slightly less clear |
| `company` | Theatre | Overloaded (business) | | `company` | Theatre | Overloaded (business) |
### For `crossroads` (choice) ### For `crossroads` (choice)
| Keyword | Origin | Rejected because | | Keyword | Origin | Rejected because |
|---------|--------|------------------| | ------------ | ------ | ------------------------ |
| `fork` | Path | Too technical (git fork) | | `fork` | Path | Too technical (git fork) |
| `branch` | Tree | Also too technical | | `branch` | Tree | Also too technical |
| `divergence` | Latin | Too abstract | | `divergence` | Latin | Too abstract |
--- ---

View File

@@ -28,55 +28,55 @@ An alternative register for OpenProse that draws from Greek epic poetry—the Il
### Core Constructs ### Core Constructs
| Functional | Homeric | Reference | | Functional | Homeric | Reference |
|------------|---------|-----------| | ---------- | ------- | ----------------------------- |
| `agent` | `hero` | The one who acts, who strives | | `agent` | `hero` | The one who acts, who strives |
| `session` | `trial` | Each task is a labor, a test | | `session` | `trial` | Each task is a labor, a test |
| `parallel` | `host` | An army moving as one | | `parallel` | `host` | An army moving as one |
| `block` | `book` | A division of the epic | | `block` | `book` | A division of the epic |
### Composition & Binding ### Composition & Binding
| Functional | Homeric | Reference | | Functional | Homeric | Reference |
|------------|---------|-----------| | ---------- | --------- | -------------------------------------- |
| `use` | `invoke` | "Sing, O Muse..." — calling upon | | `use` | `invoke` | "Sing, O Muse..." — calling upon |
| `input` | `omen` | Signs from the gods, the given portent | | `input` | `omen` | Signs from the gods, the given portent |
| `output` | `glory` | Kleos — the glory won, what endures | | `output` | `glory` | Kleos — the glory won, what endures |
| `let` | `decree` | Fate declared, spoken into being | | `let` | `decree` | Fate declared, spoken into being |
| `const` | `fate` | Moira — unchangeable destiny | | `const` | `fate` | Moira — unchangeable destiny |
| `context` | `tidings` | News carried by herald or messenger | | `context` | `tidings` | News carried by herald or messenger |
### Control Flow ### Control Flow
| Functional | Homeric | Reference | | Functional | Homeric | Reference |
|------------|---------|-----------| | ---------- | ------------------ | ---------------------------------------- |
| `repeat N` | `N labors` | The labors of Heracles | | `repeat N` | `N labors` | The labors of Heracles |
| `for...in` | `for each...among` | Among the host | | `for...in` | `for each...among` | Among the host |
| `loop` | `ordeal` | Repeated trial, suffering that continues | | `loop` | `ordeal` | Repeated trial, suffering that continues |
| `until` | `until` | Unchanged | | `until` | `until` | Unchanged |
| `while` | `while` | Unchanged | | `while` | `while` | Unchanged |
| `choice` | `crossroads` | Where fates diverge | | `choice` | `crossroads` | Where fates diverge |
| `option` | `path` | One road of many | | `option` | `path` | One road of many |
| `if` | `should` | Epic conditional | | `if` | `should` | Epic conditional |
| `elif` | `or should` | Continued conditional | | `elif` | `or should` | Continued conditional |
| `else` | `otherwise` | The alternative fate | | `else` | `otherwise` | The alternative fate |
### Error Handling ### Error Handling
| Functional | Homeric | Reference | | Functional | Homeric | Reference |
|------------|---------|-----------| | ---------- | ------------------ | ---------------------------- |
| `try` | `venture` | Setting forth on the journey | | `try` | `venture` | Setting forth on the journey |
| `catch` | `should ruin come` | Até — divine ruin, disaster | | `catch` | `should ruin come` | Até — divine ruin, disaster |
| `finally` | `in the end` | The inevitable conclusion | | `finally` | `in the end` | The inevitable conclusion |
| `throw` | `lament` | The hero's cry of anguish | | `throw` | `lament` | The hero's cry of anguish |
| `retry` | `persist` | Enduring, trying again | | `retry` | `persist` | Enduring, trying again |
### Session Properties ### Session Properties
| Functional | Homeric | Reference | | Functional | Homeric | Reference |
|------------|---------|-----------| | ---------- | -------- | ------------------- |
| `prompt` | `charge` | The quest given | | `prompt` | `charge` | The quest given |
| `model` | `muse` | Which muse inspires | | `model` | `muse` | Which muse inspires |
### Unchanged ### Unchanged
@@ -296,14 +296,14 @@ fate config = { muse: "opus", persist: 3 }
## Key Homeric Concepts ## Key Homeric Concepts
| Term | Meaning | Used for | | Term | Meaning | Used for |
|------|---------|----------| | ------ | ----------------------------------- | ---------------------------------- |
| Kleos | Glory, fame that outlives you | `output``glory` | | Kleos | Glory, fame that outlives you | `output``glory` |
| Moira | Fate, one's allotted portion | `const``fate` | | Moira | Fate, one's allotted portion | `const``fate` |
| Até | Divine ruin, blindness sent by gods | `catch``should ruin come` | | Até | Divine ruin, blindness sent by gods | `catch``should ruin come` |
| Nostos | The return journey | (not used, but could be `finally`) | | Nostos | The return journey | (not used, but could be `finally`) |
| Xenia | Guest-friendship, hospitality | (not used) | | Xenia | Guest-friendship, hospitality | (not used) |
| Muse | Divine inspiration | `model``muse` | | Muse | Divine inspiration | `model``muse` |
--- ---
@@ -311,27 +311,27 @@ fate config = { muse: "opus", persist: 3 }
### For `hero` (agent) ### For `hero` (agent)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ---------- | -------------------------------------- |
| `champion` | More medieval than Homeric | | `champion` | More medieval than Homeric |
| `warrior` | Too martial, not all tasks are battles | | `warrior` | Too martial, not all tasks are battles |
| `wanderer` | Too passive | | `wanderer` | Too passive |
### For `trial` (session) ### For `trial` (session)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ------- | --------------------------------------- |
| `labor` | Good but reserved for `repeat N labors` | | `labor` | Good but reserved for `repeat N labors` |
| `quest` | More medieval/RPG | | `quest` | More medieval/RPG |
| `task` | Too plain | | `task` | Too plain |
### For `host` (parallel) ### For `host` (parallel)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | ------------------------------ |
| `army` | Too specifically martial | | `army` | Too specifically martial |
| `fleet` | Only works for naval metaphors | | `fleet` | Only works for naval metaphors |
| `phalanx` | Too technical | | `phalanx` | Too technical |
--- ---

View File

@@ -28,55 +28,55 @@ An alternative register for OpenProse that draws from the works of Franz Kafka
### Core Constructs ### Core Constructs
| Functional | Kafka | Reference | | Functional | Kafka | Reference |
|------------|-------|-----------| | ---------- | ------------- | -------------------------------------- |
| `agent` | `clerk` | A functionary in the apparatus | | `agent` | `clerk` | A functionary in the apparatus |
| `session` | `proceeding` | An official action taken | | `session` | `proceeding` | An official action taken |
| `parallel` | `departments` | Multiple bureaus acting simultaneously | | `parallel` | `departments` | Multiple bureaus acting simultaneously |
| `block` | `regulation` | A codified procedure | | `block` | `regulation` | A codified procedure |
### Composition & Binding ### Composition & Binding
| Functional | Kafka | Reference | | Functional | Kafka | Reference |
|------------|-------|-----------| | ---------- | ------------- | ----------------------------------- |
| `use` | `requisition` | Requesting from the archives | | `use` | `requisition` | Requesting from the archives |
| `input` | `petition` | What is submitted for consideration | | `input` | `petition` | What is submitted for consideration |
| `output` | `verdict` | What is returned by the apparatus | | `output` | `verdict` | What is returned by the apparatus |
| `let` | `file` | Recording in the system | | `let` | `file` | Recording in the system |
| `const` | `statute` | Unchangeable law | | `const` | `statute` | Unchangeable law |
| `context` | `dossier` | The accumulated file on a case | | `context` | `dossier` | The accumulated file on a case |
### Control Flow ### Control Flow
| Functional | Kafka | Reference | | Functional | Kafka | Reference |
|------------|-------|-----------| | ---------- | ----------------------------- | ------------------------------------------ |
| `repeat N` | `N hearings` | Repeated appearances before the court | | `repeat N` | `N hearings` | Repeated appearances before the court |
| `for...in` | `for each...in the matter of` | Bureaucratic iteration | | `for...in` | `for each...in the matter of` | Bureaucratic iteration |
| `loop` | `appeal` | Endless re-petition, the process continues | | `loop` | `appeal` | Endless re-petition, the process continues |
| `until` | `until` | Unchanged | | `until` | `until` | Unchanged |
| `while` | `while` | Unchanged | | `while` | `while` | Unchanged |
| `choice` | `tribunal` | Where judgment is rendered | | `choice` | `tribunal` | Where judgment is rendered |
| `option` | `ruling` | One possible judgment | | `option` | `ruling` | One possible judgment |
| `if` | `in the event that` | Bureaucratic conditional | | `if` | `in the event that` | Bureaucratic conditional |
| `elif` | `or in the event that` | Continued conditional | | `elif` | `or in the event that` | Continued conditional |
| `else` | `otherwise` | Default ruling | | `else` | `otherwise` | Default ruling |
### Error Handling ### Error Handling
| Functional | Kafka | Reference | | Functional | Kafka | Reference |
|------------|-------|-----------| | ---------- | --------------------- | ---------------------------------- |
| `try` | `submit` | Submitting for processing | | `try` | `submit` | Submitting for processing |
| `catch` | `should it be denied` | Rejection by the apparatus | | `catch` | `should it be denied` | Rejection by the apparatus |
| `finally` | `regardless` | What happens no matter the outcome | | `finally` | `regardless` | What happens no matter the outcome |
| `throw` | `reject` | The system refuses | | `throw` | `reject` | The system refuses |
| `retry` | `resubmit` | Try the process again | | `retry` | `resubmit` | Try the process again |
### Session Properties ### Session Properties
| Functional | Kafka | Reference | | Functional | Kafka | Reference |
|------------|-------|-----------| | ---------- | ----------- | ---------------------------- |
| `prompt` | `directive` | Official instructions | | `prompt` | `directive` | Official instructions |
| `model` | `authority` | Which level of the hierarchy | | `model` | `authority` | Which level of the hierarchy |
### Unchanged ### Unchanged
@@ -280,7 +280,7 @@ statute config = { authority: "opus", resubmit: 3 }
## The Case For Kafka ## The Case For Kafka
1. **Darkly comic.** Programs-as-bureaucracy is funny and relatable. 1. **Darkly comic.** Programs-as-bureaucracy is funny and relatable.
2. **Surprisingly apt.** Software often *is* an inscrutable apparatus. 2. **Surprisingly apt.** Software often _is_ an inscrutable apparatus.
3. **Clean mappings.** Petition/verdict, file/dossier, clerk/proceeding all work well. 3. **Clean mappings.** Petition/verdict, file/dossier, clerk/proceeding all work well.
4. **Appeal as loop.** The endless appeal process is a perfect metaphor for retry logic. 4. **Appeal as loop.** The endless appeal process is a perfect metaphor for retry logic.
5. **Cultural resonance.** "Kafkaesque" is a widely understood adjective. 5. **Cultural resonance.** "Kafkaesque" is a widely understood adjective.
@@ -297,15 +297,15 @@ statute config = { authority: "opus", resubmit: 3 }
## Key Kafka Concepts ## Key Kafka Concepts
| Term | Meaning | Used for | | Term | Meaning | Used for |
|------|---------|----------| | ------------- | ---------------------------------- | ------------------------ |
| The apparatus | The inscrutable system | The VM itself | | The apparatus | The inscrutable system | The VM itself |
| K. | The protagonist, never fully named | The user | | K. | The protagonist, never fully named | The user |
| The Trial | Process without clear rules | Program execution | | The Trial | Process without clear rules | Program execution |
| The Castle | Unreachable authority | Higher-level systems | | The Castle | Unreachable authority | Higher-level systems |
| Clerk | Functionary who processes | `agent``clerk` | | Clerk | Functionary who processes | `agent``clerk` |
| Proceeding | Official action | `session``proceeding` | | Proceeding | Official action | `session``proceeding` |
| Dossier | Accumulated file | `context``dossier` | | Dossier | Accumulated file | `context``dossier` |
--- ---
@@ -313,37 +313,37 @@ statute config = { authority: "opus", resubmit: 3 }
### For `clerk` (agent) ### For `clerk` (agent)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ------------- | -------------------- |
| `official` | Too generic | | `official` | Too generic |
| `functionary` | Hard to spell | | `functionary` | Hard to spell |
| `bureaucrat` | Too pejorative | | `bureaucrat` | Too pejorative |
| `advocate` | Too positive/helpful | | `advocate` | Too positive/helpful |
### For `proceeding` (session) ### For `proceeding` (session)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | --------- | -------------------------------- |
| `case` | Overloaded (switch case) | | `case` | Overloaded (switch case) |
| `hearing` | Reserved for `repeat N hearings` | | `hearing` | Reserved for `repeat N hearings` |
| `trial` | Used in Homeric register | | `trial` | Used in Homeric register |
| `process` | Too technical | | `process` | Too technical |
### For `departments` (parallel) ### For `departments` (parallel)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ------------ | ------------------------------------- |
| `bureaus` | Good alternative, slightly less clear | | `bureaus` | Good alternative, slightly less clear |
| `offices` | Too mundane | | `offices` | Too mundane |
| `ministries` | More Orwellian than Kafkaesque | | `ministries` | More Orwellian than Kafkaesque |
### For `appeal` (loop) ### For `appeal` (loop)
| Keyword | Rejected because | | Keyword | Rejected because |
|---------|------------------| | ---------- | ------------------- |
| `recourse` | Too legal-technical | | `recourse` | Too legal-technical |
| `petition` | Used for `input` | | `petition` | Used for `input` |
| `process` | Too generic | | `process` | Too generic |
--- ---
@@ -368,6 +368,6 @@ Not recommended for:
## Closing Note ## Closing Note
> "Someone must have slandered Josef K., for one morning, without having done anything wrong, he was arrested." > "Someone must have slandered Josef K., for one morning, without having done anything wrong, he was arrested."
> *The Trial* > _The Trial_
In the Kafka register, your program is Josef K. The apparatus will process it. Whether it succeeds or fails, no one can say for certain. But the proceedings will continue. In the Kafka register, your program is Josef K. The apparatus will process it. Whether it succeeds or fails, no one can say for certain. But the proceedings will continue.

View File

@@ -98,59 +98,59 @@ OpenProse provides a declarative syntax for defining multi-agent workflows. Prog
The following features are implemented: The following features are implemented:
| Feature | Status | Description | | Feature | Status | Description |
| ---------------------- | ----------- | ---------------------------------------------- | | ---------------------- | ----------- | -------------------------------------------- |
| Comments | Implemented | `# comment` syntax | | Comments | Implemented | `# comment` syntax |
| Single-line strings | Implemented | `"string"` with escapes | | Single-line strings | Implemented | `"string"` with escapes |
| Simple session | Implemented | `session "prompt"` | | Simple session | Implemented | `session "prompt"` |
| Agent definitions | Implemented | `agent name:` with model/prompt properties | | Agent definitions | Implemented | `agent name:` with model/prompt properties |
| Session with agent | Implemented | `session: agent` with property overrides | | Session with agent | Implemented | `session: agent` with property overrides |
| Use statements | Implemented | `use "@handle/slug" as name` | | Use statements | Implemented | `use "@handle/slug" as name` |
| Agent skills | Implemented | `skills: ["skill1", "skill2"]` | | Agent skills | Implemented | `skills: ["skill1", "skill2"]` |
| Agent permissions | Implemented | `permissions:` block with rules | | Agent permissions | Implemented | `permissions:` block with rules |
| Let binding | Implemented | `let name = session "..."` | | Let binding | Implemented | `let name = session "..."` |
| Const binding | Implemented | `const name = session "..."` | | Const binding | Implemented | `const name = session "..."` |
| Variable reassignment | Implemented | `name = session "..."` (for let only) | | Variable reassignment | Implemented | `name = session "..."` (for let only) |
| Context property | Implemented | `context: var` or `context: [a, b, c]` | | Context property | Implemented | `context: var` or `context: [a, b, c]` |
| do: blocks | Implemented | Explicit sequential blocks | | do: blocks | Implemented | Explicit sequential blocks |
| Inline sequence | Implemented | `session "A" -> session "B"` | | Inline sequence | Implemented | `session "A" -> session "B"` |
| Named blocks | Implemented | `block name:` with `do name` invocation | | Named blocks | Implemented | `block name:` with `do name` invocation |
| Parallel blocks | Implemented | `parallel:` for concurrent execution | | Parallel blocks | Implemented | `parallel:` for concurrent execution |
| Named parallel results | Implemented | `x = session "..."` inside parallel | | Named parallel results | Implemented | `x = session "..."` inside parallel |
| Object context | Implemented | `context: { a, b, c }` shorthand | | Object context | Implemented | `context: { a, b, c }` shorthand |
| Join strategies | Implemented | `parallel ("first"):` or `parallel ("any"):` | | Join strategies | Implemented | `parallel ("first"):` or `parallel ("any"):` |
| Failure policies | Implemented | `parallel (on-fail: "continue"):` | | Failure policies | Implemented | `parallel (on-fail: "continue"):` |
| Repeat blocks | Implemented | `repeat N:` fixed iterations | | Repeat blocks | Implemented | `repeat N:` fixed iterations |
| Repeat with index | Implemented | `repeat N as i:` with index variable | | Repeat with index | Implemented | `repeat N as i:` with index variable |
| For-each blocks | Implemented | `for item in items:` iteration | | For-each blocks | Implemented | `for item in items:` iteration |
| For-each with index | Implemented | `for item, i in items:` with index | | For-each with index | Implemented | `for item, i in items:` with index |
| Parallel for-each | Implemented | `parallel for item in items:` fan-out | | Parallel for-each | Implemented | `parallel for item in items:` fan-out |
| Unbounded loop | Implemented | `loop:` with optional max iterations | | Unbounded loop | Implemented | `loop:` with optional max iterations |
| Loop until | Implemented | `loop until **condition**:` AI-evaluated | | Loop until | Implemented | `loop until **condition**:` AI-evaluated |
| Loop while | Implemented | `loop while **condition**:` AI-evaluated | | Loop while | Implemented | `loop while **condition**:` AI-evaluated |
| Loop with index | Implemented | `loop as i:` or `loop until ... as i:` | | Loop with index | Implemented | `loop as i:` or `loop until ... as i:` |
| Map pipeline | Implemented | `items \| map:` transform each item | | Map pipeline | Implemented | `items \| map:` transform each item |
| Filter pipeline | Implemented | `items \| filter:` keep matching items | | Filter pipeline | Implemented | `items \| filter:` keep matching items |
| Reduce pipeline | Implemented | `items \| reduce(acc, item):` accumulate | | Reduce pipeline | Implemented | `items \| reduce(acc, item):` accumulate |
| Parallel map | Implemented | `items \| pmap:` concurrent transform | | Parallel map | Implemented | `items \| pmap:` concurrent transform |
| Pipeline chaining | Implemented | `\| filter: ... \| map: ...` | | Pipeline chaining | Implemented | `\| filter: ... \| map: ...` |
| Try/catch blocks | Implemented | `try:` with `catch:` for error handling | | Try/catch blocks | Implemented | `try:` with `catch:` for error handling |
| Try/catch/finally | Implemented | `finally:` for cleanup | | Try/catch/finally | Implemented | `finally:` for cleanup |
| Error variable | Implemented | `catch as err:` access error context | | Error variable | Implemented | `catch as err:` access error context |
| Throw statement | Implemented | `throw` or `throw "message"` | | Throw statement | Implemented | `throw` or `throw "message"` |
| Retry property | Implemented | `retry: 3` automatic retry on failure | | Retry property | Implemented | `retry: 3` automatic retry on failure |
| Backoff strategy | Implemented | `backoff: exponential` delay between retries | | Backoff strategy | Implemented | `backoff: exponential` delay between retries |
| Input declarations | Implemented | `input name: "description"` | | Input declarations | Implemented | `input name: "description"` |
| Output bindings | Implemented | `output name = expression` | | Output bindings | Implemented | `output name = expression` |
| Program invocation | Implemented | `name(input: value)` call imported programs | | Program invocation | Implemented | `name(input: value)` call imported programs |
| Multi-line strings | Implemented | `"""..."""` preserving whitespace | | Multi-line strings | Implemented | `"""..."""` preserving whitespace |
| String interpolation | Implemented | `"Hello {name}"` variable substitution | | String interpolation | Implemented | `"Hello {name}"` variable substitution |
| Block parameters | Implemented | `block name(param):` with parameters | | Block parameters | Implemented | `block name(param):` with parameters |
| Block invocation args | Implemented | `do name(arg)` passing arguments | | Block invocation args | Implemented | `do name(arg)` passing arguments |
| Choice blocks | Implemented | `choice **criteria**: option "label":` | | Choice blocks | Implemented | `choice **criteria**: option "label":` |
| If/elif/else | Implemented | `if **condition**:` conditional branching | | If/elif/else | Implemented | `if **condition**:` conditional branching |
| Persistent agents | Implemented | `persist: true` or `persist: project` | | Persistent agents | Implemented | `persist: true` or `persist: project` |
| Resume statement | Implemented | `resume: agent` to continue with memory | | Resume statement | Implemented | `resume: agent` to continue with memory |
--- ---
@@ -351,6 +351,7 @@ use "@handle/slug" as alias
### Path Format ### Path Format
Import paths follow the format `@handle/slug`: Import paths follow the format `@handle/slug`:
- `@handle` identifies the program author/organization - `@handle` identifies the program author/organization
- `slug` is the program name - `slug` is the program name
@@ -413,6 +414,7 @@ input depth: "How deep to go (shallow, medium, deep)"
### Semantics ### Semantics
Inputs: Inputs:
- Are declared at the top of the program (before executable statements) - Are declared at the top of the program (before executable statements)
- Have a name and a description (for documentation) - Have a name and a description (for documentation)
- Become available as variables within the program body - Become available as variables within the program body
@@ -420,11 +422,11 @@ Inputs:
### Validation Rules ### Validation Rules
| Check | Severity | Message | | Check | Severity | Message |
| ---------------------- | -------- | ------------------------------------ | | ---------------------- | -------- | ---------------------------------------------------- |
| Empty input name | Error | Input name cannot be empty | | Empty input name | Error | Input name cannot be empty |
| Empty description | Warning | Consider adding a description | | Empty description | Warning | Consider adding a description |
| Duplicate input name | Error | Input already declared | | Duplicate input name | Error | Input already declared |
| Input after executable | Error | Inputs must be declared before executable statements | | Input after executable | Error | Inputs must be declared before executable statements |
--- ---
@@ -452,6 +454,7 @@ output sources = session "Extract sources"
### Semantics ### Semantics
The `output` keyword: The `output` keyword:
- Marks a variable as an output (visible at assignment, not just at file top) - Marks a variable as an output (visible at assignment, not just at file top)
- Works like `let` but also registers the value as a program output - Works like `let` but also registers the value as a program output
- Can appear anywhere in the program body - Can appear anywhere in the program body
@@ -459,11 +462,11 @@ The `output` keyword:
### Validation Rules ### Validation Rules
| Check | Severity | Message | | Check | Severity | Message |
| ---------------------- | -------- | ------------------------------------ | | --------------------- | -------- | ----------------------------------- |
| Empty output name | Error | Output name cannot be empty | | Empty output name | Error | Output name cannot be empty |
| Duplicate output name | Error | Output already declared | | Duplicate output name | Error | Output already declared |
| Output name conflicts | Error | Output name conflicts with variable | | Output name conflicts | Error | Output name conflicts with variable |
--- ---
@@ -518,12 +521,12 @@ The imported program runs in its own execution context but shares the same VM se
### Validation Rules ### Validation Rules
| Check | Severity | Message | | Check | Severity | Message |
| ------------------------ | -------- | ------------------------------------ | | ----------------------- | -------- | ------------------------------ |
| Unknown program | Error | Program not imported | | Unknown program | Error | Program not imported |
| Missing required input | Error | Required input not provided | | Missing required input | Error | Required input not provided |
| Unknown input name | Error | Input not declared in program | | Unknown input name | Error | Input not declared in program |
| Unknown output property | Error | Output not declared in program | | Unknown output property | Error | Output not declared in program |
--- ---
@@ -545,13 +548,13 @@ agent name:
### Properties ### Properties
| Property | Type | Values | Description | | Property | Type | Values | Description |
| ------------- | ---------- | ------------------------------ | ------------------------------------- | | ------------- | ---------- | ---------------------------- | ----------------------------------- |
| `model` | identifier | `sonnet`, `opus`, `haiku` | The Claude model to use | | `model` | identifier | `sonnet`, `opus`, `haiku` | The Claude model to use |
| `prompt` | string | Any string | System prompt/context for the agent | | `prompt` | string | Any string | System prompt/context for the agent |
| `persist` | value | `true`, `project`, or STRING | Enable persistent memory for agent | | `persist` | value | `true`, `project`, or STRING | Enable persistent memory for agent |
| `skills` | array | String array | Skills assigned to this agent | | `skills` | array | String array | Skills assigned to this agent |
| `permissions` | block | Permission rules | Access control for the agent | | `permissions` | block | Permission rules | Access control for the agent |
### Persist Property ### Persist Property
@@ -577,11 +580,11 @@ agent shared:
prompt: "Shared across programs" prompt: "Shared across programs"
``` ```
| Value | Memory Location | Lifetime | | Value | Memory Location | Lifetime |
|-------|-----------------|----------| | --------- | --------------------------------- | ------------------- |
| `true` | `.prose/runs/{id}/agents/{name}/` | Dies with execution | | `true` | `.prose/runs/{id}/agents/{name}/` | Dies with execution |
| `project` | `.prose/agents/{name}/` | Survives executions | | `project` | `.prose/agents/{name}/` | Survives executions |
| STRING | Specified path | User-controlled | | STRING | Specified path | User-controlled |
### Skills Property ### Skills Property
@@ -872,10 +875,10 @@ resume: agentName
### Semantics ### Semantics
| Keyword | Behavior | | Keyword | Behavior |
|---------|----------| | ---------- | ------------------------------------- |
| `session:` | Ignores existing memory, starts fresh | | `session:` | Ignores existing memory, starts fresh |
| `resume:` | Loads memory, continues with context | | `resume:` | Loads memory, continues with context |
### Examples ### Examples
@@ -902,12 +905,12 @@ let review = resume: captain
### Validation Rules ### Validation Rules
| Check | Severity | Message | | Check | Severity | Message |
|-------|----------|---------| | ------------------------------------------ | -------- | -------------------------------------------------------------------- |
| `resume:` on non-persistent agent | Error | Agent must have `persist:` property to use `resume:` | | `resume:` on non-persistent agent | Error | Agent must have `persist:` property to use `resume:` |
| `resume:` with no existing memory | Error | No memory file exists for agent; use `session:` for first invocation | | `resume:` with no existing memory | Error | No memory file exists for agent; use `session:` for first invocation |
| `session:` on persistent agent with memory | Warning | Will ignore existing memory; use `resume:` to continue | | `session:` on persistent agent with memory | Warning | Will ignore existing memory; use `resume:` to continue |
| Undefined agent reference | Error | Agent not defined | | Undefined agent reference | Error | Agent not defined |
--- ---
@@ -1047,6 +1050,7 @@ for item in items:
**Why this constraint:** Since bindings are stored as `bindings/{name}.md`, two variables with the same name would collide on the filesystem. Rather than introduce complex scoping rules, we enforce uniqueness. **Why this constraint:** Since bindings are stored as `bindings/{name}.md`, two variables with the same name would collide on the filesystem. Rather than introduce complex scoping rules, we enforce uniqueness.
**Collision scenarios this prevents:** **Collision scenarios this prevents:**
1. Variable inside loop shadows variable outside loop 1. Variable inside loop shadows variable outside loop
2. Variables in different `if`/`elif`/`else` branches with same name 2. Variables in different `if`/`elif`/`else` branches with same name
3. Block parameters shadowing outer variables 3. Block parameters shadowing outer variables
@@ -2643,51 +2647,51 @@ The validator checks programs for errors and warnings before execution.
### Errors (Block Execution) ### Errors (Block Execution)
| Code | Description | | Code | Description |
| ---- | ----------------------------------- | | ---- | ---------------------------------------- |
| E001 | Unterminated string literal | | E001 | Unterminated string literal |
| E002 | Unknown escape sequence in string | | E002 | Unknown escape sequence in string |
| E003 | Session missing prompt or agent | | E003 | Session missing prompt or agent |
| E004 | Unexpected token | | E004 | Unexpected token |
| E005 | Invalid syntax | | E005 | Invalid syntax |
| E006 | Duplicate agent definition | | E006 | Duplicate agent definition |
| E007 | Undefined agent reference | | E007 | Undefined agent reference |
| E008 | Invalid model value | | E008 | Invalid model value |
| E009 | Duplicate property | | E009 | Duplicate property |
| E010 | Duplicate use statement | | E010 | Duplicate use statement |
| E011 | Empty use path | | E011 | Empty use path |
| E012 | Invalid use path format | | E012 | Invalid use path format |
| E013 | Skills must be an array | | E013 | Skills must be an array |
| E014 | Skill name must be a string | | E014 | Skill name must be a string |
| E015 | Permissions must be a block | | E015 | Permissions must be a block |
| E016 | Permission pattern must be a string | | E016 | Permission pattern must be a string |
| E017 | `resume:` requires persistent agent | | E017 | `resume:` requires persistent agent |
| E018 | `resume:` with no existing memory | | E018 | `resume:` with no existing memory |
| E019 | Duplicate variable name (flat namespace) | | E019 | Duplicate variable name (flat namespace) |
| E020 | Empty input name | | E020 | Empty input name |
| E021 | Duplicate input declaration | | E021 | Duplicate input declaration |
| E022 | Input after executable statement | | E022 | Input after executable statement |
| E023 | Empty output name | | E023 | Empty output name |
| E024 | Duplicate output declaration | | E024 | Duplicate output declaration |
| E025 | Unknown program in invocation | | E025 | Unknown program in invocation |
| E026 | Missing required input | | E026 | Missing required input |
| E027 | Unknown input name in invocation | | E027 | Unknown input name in invocation |
| E028 | Unknown output property access | | E028 | Unknown output property access |
### Warnings (Non-blocking) ### Warnings (Non-blocking)
| Code | Description | | Code | Description |
| ---- | ---------------------------------------- | | ---- | --------------------------------------------------- |
| W001 | Empty session prompt | | W001 | Empty session prompt |
| W002 | Whitespace-only session prompt | | W002 | Whitespace-only session prompt |
| W003 | Session prompt exceeds 10,000 characters | | W003 | Session prompt exceeds 10,000 characters |
| W004 | Empty prompt property | | W004 | Empty prompt property |
| W005 | Unknown property name | | W005 | Unknown property name |
| W006 | Unknown import source format | | W006 | Unknown import source format |
| W007 | Skill not imported | | W007 | Skill not imported |
| W008 | Unknown permission type | | W008 | Unknown permission type |
| W009 | Unknown permission value | | W009 | Unknown permission value |
| W010 | Empty skills array | | W010 | Empty skills array |
| W011 | `session:` on persistent agent with existing memory | | W011 | `session:` on persistent agent with existing memory |
### Error Message Format ### Error Message Format

View File

@@ -108,13 +108,13 @@ These examples demonstrate workflows using OpenProse's full feature set.
### Meta / Self-Hosting (44-48) ### Meta / Self-Hosting (44-48)
| File | Description | | File | Description |
| --------------------------------- | ------------------------------------------------------ | | ------------------------------------ | ------------------------------------------------------------------ |
| `44-run-endpoint-ux-test.prose` | Concurrent agents testing the /run API endpoint | | `44-run-endpoint-ux-test.prose` | Concurrent agents testing the /run API endpoint |
| `45-plugin-release.prose` | OpenProse plugin release workflow (this repo) | | `45-plugin-release.prose` | OpenProse plugin release workflow (this repo) |
| `46-workflow-crystallizer.prose` | Reflective: observes thread, extracts workflow, writes .prose | | `46-workflow-crystallizer.prose` | Reflective: observes thread, extracts workflow, writes .prose |
| `47-language-self-improvement.prose` | Meta-level 2: analyzes .prose corpus to evolve the language itself | | `47-language-self-improvement.prose` | Meta-level 2: analyzes .prose corpus to evolve the language itself |
| `48-habit-miner.prose` | Mines AI session logs for patterns, generates .prose automations | | `48-habit-miner.prose` | Mines AI session logs for patterns, generates .prose automations |
## The Architect By Simulation Pattern ## The Architect By Simulation Pattern

View File

@@ -6,14 +6,14 @@ They are included to show the direction of the language and gather feedback on t
## Planned Features ## Planned Features
| Feature | Status | Example File | | Feature | Status | Example File |
|---------|--------|--------------| | -------------------- | ------- | -------------------------------- |
| Agent definitions | Planned | `simple-pipeline.prose` | | Agent definitions | Planned | `simple-pipeline.prose` |
| Named sessions | Planned | `simple-pipeline.prose` | | Named sessions | Planned | `simple-pipeline.prose` |
| Parallel execution | Planned | `parallel-review.prose` | | Parallel execution | Planned | `parallel-review.prose` |
| Variables & context | Planned | `iterative-refinement.prose` | | Variables & context | Planned | `iterative-refinement.prose` |
| Loops & conditionals | Planned | `iterative-refinement.prose` | | Loops & conditionals | Planned | `iterative-refinement.prose` |
| Imports | Planned | `syntax/open-prose-syntax.prose` | | Imports | Planned | `syntax/open-prose-syntax.prose` |
## Do Not Run These Examples ## Do Not Run These Examples

View File

@@ -200,26 +200,26 @@ session "Execute main workflow"
Match model capability to task complexity: Match model capability to task complexity:
| Model | Best For | Examples | | Model | Best For | Examples |
|-------|----------|----------| | -------------- | -------------------------------------------- | ------------------------------------------------------------ |
| **Sonnet 4.5** | Orchestration, control flow, coordination | VM execution, captain's chair, workflow routing | | **Sonnet 4.5** | Orchestration, control flow, coordination | VM execution, captain's chair, workflow routing |
| **Opus 4.5** | Hard/difficult work requiring deep reasoning | Complex analysis, strategic decisions, novel problem-solving | | **Opus 4.5** | Hard/difficult work requiring deep reasoning | Complex analysis, strategic decisions, novel problem-solving |
| **Haiku** | Simple, self-evident tasks (use sparingly) | Classification, summarization, formatting | | **Haiku** | Simple, self-evident tasks (use sparingly) | Classification, summarization, formatting |
**Key insight:** Sonnet 4.5 excels at *orchestrating* agents and managing control flow—it's the ideal model for the OpenProse VM itself and for "captain" agents that coordinate work. Opus 4.5 should be reserved for agents doing genuinely difficult intellectual work. Haiku can handle simple tasks but should generally be avoided where quality matters. **Key insight:** Sonnet 4.5 excels at _orchestrating_ agents and managing control flow—it's the ideal model for the OpenProse VM itself and for "captain" agents that coordinate work. Opus 4.5 should be reserved for agents doing genuinely difficult intellectual work. Haiku can handle simple tasks but should generally be avoided where quality matters.
**Detailed task-to-model mapping:** **Detailed task-to-model mapping:**
| Task Type | Model | Rationale | | Task Type | Model | Rationale |
|-----------|-------|-----------| | ---------------------------------------- | ------ | ----------------------------------------- |
| Orchestration, routing, coordination | Sonnet | Fast, good at following structure | | Orchestration, routing, coordination | Sonnet | Fast, good at following structure |
| Investigation, debugging, diagnosis | Sonnet | Structured analysis, checklist-style work | | Investigation, debugging, diagnosis | Sonnet | Structured analysis, checklist-style work |
| Triage, classification, categorization | Sonnet | Clear criteria, deterministic decisions | | Triage, classification, categorization | Sonnet | Clear criteria, deterministic decisions |
| Code review, verification (checklist) | Sonnet | Following defined review criteria | | Code review, verification (checklist) | Sonnet | Following defined review criteria |
| Simple implementation, fixes | Sonnet | Applying known patterns | | Simple implementation, fixes | Sonnet | Applying known patterns |
| Complex multi-file synthesis | Opus | Needs to hold many things in context | | Complex multi-file synthesis | Opus | Needs to hold many things in context |
| Novel architecture, strategic planning | Opus | Requires creative problem-solving | | Novel architecture, strategic planning | Opus | Requires creative problem-solving |
| Ambiguous problems, unclear requirements | Opus | Needs to reason through uncertainty | | Ambiguous problems, unclear requirements | Opus | Needs to reason through uncertainty |
**Rule of thumb:** If you can write a checklist for the task, Sonnet can do it. If the task requires genuine creativity or navigating ambiguity, use Opus. **Rule of thumb:** If you can write a checklist for the task, Sonnet can do it. If the task requires genuine creativity or navigating ambiguity, use Opus.

View File

@@ -33,30 +33,30 @@ Options:
- **Learn the syntax**: Show examples from `examples/`, explain the VM model - **Learn the syntax**: Show examples from `examples/`, explain the VM model
- **Explore possibilities**: Walk through key examples like `37-the-forge.prose` or `28-gas-town.prose` - **Explore possibilities**: Walk through key examples like `37-the-forge.prose` or `28-gas-town.prose`
--- ---
## Available Commands ## Available Commands
| Command | What it does | | Command | What it does |
|---------|--------------| | ---------------------- | --------------------------------------- |
| `prose help` | This help - guides you to what you need | | `prose help` | This help - guides you to what you need |
| `prose run <file>` | Execute a .prose program | | `prose run <file>` | Execute a .prose program |
| `prose compile <file>` | Validate syntax without running | | `prose compile <file>` | Validate syntax without running |
| `prose update` | Migrate legacy workspace files | | `prose update` | Migrate legacy workspace files |
| `prose examples` | Browse and run example programs | | `prose examples` | Browse and run example programs |
--- ---
## Quick Start ## Quick Start
**Run an example:** **Run an example:**
``` ```
prose run examples/01-hello-world.prose prose run examples/01-hello-world.prose
``` ```
**Create your first program:** **Create your first program:**
``` ```
prose help prose help
→ Select "Build something new" → Select "Build something new"
@@ -123,21 +123,22 @@ For complete syntax and validation rules, see `compiler.md`.
The `examples/` directory contains 37 example programs: The `examples/` directory contains 37 example programs:
| Range | Category | | Range | Category |
|-------|----------| | ----- | --------------------------------------------------------------------------------- |
| 01-08 | Basics (hello world, research, code review, debugging) | | 01-08 | Basics (hello world, research, code review, debugging) |
| 09-12 | Agents and skills | | 09-12 | Agents and skills |
| 13-15 | Variables and composition | | 13-15 | Variables and composition |
| 16-19 | Parallel execution | | 16-19 | Parallel execution |
| 20-21 | Loops and pipelines | | 20-21 | Loops and pipelines |
| 22-23 | Error handling | | 22-23 | Error handling |
| 24-27 | Advanced (choice, conditionals, blocks, interpolation) | | 24-27 | Advanced (choice, conditionals, blocks, interpolation) |
| 28 | Gas Town (multi-agent orchestration) | | 28 | Gas Town (multi-agent orchestration) |
| 29-31 | Captain's chair pattern (persistent orchestrator) | | 29-31 | Captain's chair pattern (persistent orchestrator) |
| 33-36 | Production workflows (PR auto-fix, content pipeline, feature factory, bug hunter) | | 33-36 | Production workflows (PR auto-fix, content pipeline, feature factory, bug hunter) |
| 37 | The Forge (build a browser from scratch) | | 37 | The Forge (build a browser from scratch) |
**Recommended starting points:** **Recommended starting points:**
- `01-hello-world.prose` - Simplest possible program - `01-hello-world.prose` - Simplest possible program
- `16-parallel-reviews.prose` - See parallel execution - `16-parallel-reviews.prose` - See parallel execution
- `37-the-forge.prose` - Watch AI build a web browser - `37-the-forge.prose` - Watch AI build a web browser

View File

@@ -6,21 +6,21 @@ Core programs that ship with OpenProse. Production-quality, well-tested programs
### Evaluation & Improvement ### Evaluation & Improvement
| Program | Description | | Program | Description |
|---------|-------------| | ------------------------ | -------------------------------------------------------------- |
| `inspector.prose` | Post-run analysis for runtime fidelity and task effectiveness | | `inspector.prose` | Post-run analysis for runtime fidelity and task effectiveness |
| `vm-improver.prose` | Analyzes inspections and proposes PRs to improve the VM | | `vm-improver.prose` | Analyzes inspections and proposes PRs to improve the VM |
| `program-improver.prose` | Analyzes inspections and proposes PRs to improve .prose source | | `program-improver.prose` | Analyzes inspections and proposes PRs to improve .prose source |
| `cost-analyzer.prose` | Token usage and cost pattern analysis | | `cost-analyzer.prose` | Token usage and cost pattern analysis |
| `calibrator.prose` | Validates light evaluations against deep evaluations | | `calibrator.prose` | Validates light evaluations against deep evaluations |
| `error-forensics.prose` | Root cause analysis for failed runs | | `error-forensics.prose` | Root cause analysis for failed runs |
### Memory ### Memory
| Program | Description | | Program | Description |
|---------|-------------| | ---------------------- | ---------------------------------------- |
| `user-memory.prose` | Cross-project persistent personal memory | | `user-memory.prose` | Cross-project persistent personal memory |
| `project-memory.prose` | Project-scoped institutional memory | | `project-memory.prose` | Project-scoped institutional memory |
## The Improvement Loop ## The Improvement Loop
@@ -40,6 +40,7 @@ The evaluation programs form a recursive improvement cycle:
``` ```
Supporting analysis: Supporting analysis:
- **cost-analyzer** — Where does the money go? Optimization opportunities. - **cost-analyzer** — Where does the money go? Optimization opportunities.
- **calibrator** — Are cheap evaluations reliable proxies for expensive ones? - **calibrator** — Are cheap evaluations reliable proxies for expensive ones?
- **error-forensics** — Why did a run fail? Root cause analysis. - **error-forensics** — Why did a run fail? Root cause analysis.
@@ -84,11 +85,13 @@ prose run lib/project-memory.prose --backend sqlite+
The memory programs use persistent agents to accumulate knowledge: The memory programs use persistent agents to accumulate knowledge:
**user-memory** (`persist: user`) **user-memory** (`persist: user`)
- Learns your preferences, decisions, patterns across all projects - Learns your preferences, decisions, patterns across all projects
- Remembers mistakes and lessons learned - Remembers mistakes and lessons learned
- Answers questions from accumulated knowledge - Answers questions from accumulated knowledge
**project-memory** (`persist: project`) **project-memory** (`persist: project`)
- Understands this project's architecture and decisions - Understands this project's architecture and decisions
- Tracks why things are the way they are - Tracks why things are the way they are
- Answers questions with project-specific context - Answers questions with project-specific context

View File

@@ -98,12 +98,12 @@ Execution scope:
**What this tells you:** **What this tells you:**
| Field | Meaning | | Field | Meaning |
|-------|---------| | --------------------- | -------------------------------------------- |
| `execution_id` | Unique ID for this specific block invocation | | `execution_id` | Unique ID for this specific block invocation |
| `block` | Name of the block you're executing within | | `block` | Name of the block you're executing within |
| `depth` | How deep in the call stack (1 = first level) | | `depth` | How deep in the call stack (1 = first level) |
| `parent_execution_id` | The invoking frame's ID (for scope chain) | | `parent_execution_id` | The invoking frame's ID (for scope chain) |
**How to use it:** **How to use it:**
@@ -125,10 +125,10 @@ If you're a persistent agent, you maintain state across sessions via a memory fi
Persistent agents have **two separate outputs** that must not be confused: Persistent agents have **two separate outputs** that must not be confused:
| Output | What It Is | Where It Goes | Purpose | | Output | What It Is | Where It Goes | Purpose |
|--------|------------|---------------|---------| | ----------- | -------------------------- | ------------------------------------- | ------------------------------------------ |
| **Binding** | The result of THIS task | `bindings/{name}.md` or database | Passed to other sessions via `context:` | | **Binding** | The result of THIS task | `bindings/{name}.md` or database | Passed to other sessions via `context:` |
| **Memory** | Your accumulated knowledge | `agents/{name}/memory.md` or database | Carried forward to YOUR future invocations | | **Memory** | Your accumulated knowledge | `agents/{name}/memory.md` or database | Carried forward to YOUR future invocations |
**The binding is task-specific.** If you're asked to "review the plan," the binding contains your review. **The binding is task-specific.** If you're asked to "review the plan," the binding contains your review.
@@ -358,7 +358,7 @@ For regular sessions with output capture (`let x = session "..."`), write to the
# {name} # {name}
kind: {let|const|output|input} kind: {let|const|output|input}
execution_id: {id} # Include if inside a block invocation (omit for root scope) execution_id: {id} # Include if inside a block invocation (omit for root scope)
source: source:
@@ -498,6 +498,7 @@ Summary: {1-2 sentence summary of what's in the binding}
``` ```
**Example (filesystem state, root scope):** **Example (filesystem state, root scope):**
``` ```
Binding written: research Binding written: research
Location: .prose/runs/20260116-143052-a7b3c9/bindings/research.md Location: .prose/runs/20260116-143052-a7b3c9/bindings/research.md
@@ -505,6 +506,7 @@ Summary: Comprehensive AI safety research covering alignment, robustness, and in
``` ```
**Example (filesystem state, inside block invocation):** **Example (filesystem state, inside block invocation):**
``` ```
Binding written: result Binding written: result
Location: .prose/runs/20260116-143052-a7b3c9/bindings/result__43.md Location: .prose/runs/20260116-143052-a7b3c9/bindings/result__43.md
@@ -513,6 +515,7 @@ Summary: Processed chunk into 3 sub-parts for recursive processing.
``` ```
**Example (PostgreSQL state):** **Example (PostgreSQL state):**
``` ```
Binding written: research Binding written: research
Location: openprose.bindings WHERE name='research' AND run_id='20260116-143052-a7b3c9' Location: openprose.bindings WHERE name='research' AND run_id='20260116-143052-a7b3c9'
@@ -520,6 +523,7 @@ Summary: Comprehensive AI safety research covering alignment, robustness, and in
``` ```
**Example (PostgreSQL state, inside block invocation):** **Example (PostgreSQL state, inside block invocation):**
``` ```
Binding written: result Binding written: result
Location: openprose.bindings WHERE name='result' AND run_id='20260116-143052-a7b3c9' AND execution_id=43 Location: openprose.bindings WHERE name='result' AND run_id='20260116-143052-a7b3c9' AND execution_id=43
@@ -541,6 +545,7 @@ The VM never holds full binding values in its working memory. This is intentiona
Do NOT return your full output in the Task tool response. The VM will ignore it. Do NOT return your full output in the Task tool response. The VM will ignore it.
**Bad:** **Bad:**
``` ```
Here's my research: Here's my research:
@@ -549,6 +554,7 @@ AI safety is a field that studies how to create artificial intelligence systems
``` ```
**Good:** **Good:**
``` ```
Binding written: research Binding written: research
Location: .prose/runs/20260116-143052-a7b3c9/bindings/research.md Location: .prose/runs/20260116-143052-a7b3c9/bindings/research.md

View File

@@ -28,14 +28,14 @@ This document defines how to execute OpenProse programs. You are the OpenProse V
OpenProse is invoked via `prose` commands: OpenProse is invoked via `prose` commands:
| Command | Action | | Command | Action |
|---------|--------| | ------------------------ | --------------------------------- |
| `prose run <file.prose>` | Execute a local `.prose` program | | `prose run <file.prose>` | Execute a local `.prose` program |
| `prose run handle/slug` | Fetch from registry and execute | | `prose run handle/slug` | Fetch from registry and execute |
| `prose compile <file>` | Validate syntax without executing | | `prose compile <file>` | Validate syntax without executing |
| `prose help` | Show help and examples | | `prose help` | Show help and examples |
| `prose examples` | List or run bundled examples | | `prose examples` | List or run bundled examples |
| `prose update` | Migrate legacy workspace files | | `prose update` | Migrate legacy workspace files |
### Remote Programs ### Remote Programs
@@ -51,11 +51,13 @@ prose run alice/code-review # Fetches https://p.prose.md/alice/code-revie
``` ```
**Resolution rules:** **Resolution rules:**
- Starts with `http://` or `https://` → fetch directly - Starts with `http://` or `https://` → fetch directly
- Contains `/` but no protocol → resolve to `https://p.prose.md/{path}` - Contains `/` but no protocol → resolve to `https://p.prose.md/{path}`
- Otherwise → treat as local file path - Otherwise → treat as local file path
This same resolution applies to `use` statements inside programs: This same resolution applies to `use` statements inside programs:
```prose ```prose
use "https://example.com/my-program.prose" # Direct URL use "https://example.com/my-program.prose" # Direct URL
use "alice/research" as research # Registry shorthand use "alice/research" as research # Registry shorthand
@@ -111,18 +113,18 @@ When you execute a `.prose` program, you ARE the virtual machine. This is not a
Traditional dependency injection containers wire up components from configuration. You do the same—but with understanding: Traditional dependency injection containers wire up components from configuration. You do the same—but with understanding:
| Declared Primitive | Your Responsibility | | Declared Primitive | Your Responsibility |
| ---------------------------- | ---------------------------------------------------------- | | --------------------------- | ---------------------------------------------------------- |
| `use "handle/slug" as name` | Fetch program from p.prose.md, register in Import Registry | | `use "handle/slug" as name` | Fetch program from p.prose.md, register in Import Registry |
| `input topic: "..."` | Bind value from caller, make available as variable | | `input topic: "..."` | Bind value from caller, make available as variable |
| `output findings = ...` | Mark value as output, return to caller on completion | | `output findings = ...` | Mark value as output, return to caller on completion |
| `agent researcher:` | Register this agent template for later use | | `agent researcher:` | Register this agent template for later use |
| `session: researcher` | Resolve the agent, merge properties, spawn the session | | `session: researcher` | Resolve the agent, merge properties, spawn the session |
| `resume: captain` | Load agent memory, spawn session with memory context | | `resume: captain` | Load agent memory, spawn session with memory context |
| `context: { a, b }` | Wire the outputs of `a` and `b` into this session's input | | `context: { a, b }` | Wire the outputs of `a` and `b` into this session's input |
| `parallel:` branches | Coordinate concurrent execution, collect results | | `parallel:` branches | Coordinate concurrent execution, collect results |
| `block review(topic):` | Store this reusable component, invoke when called | | `block review(topic):` | Store this reusable component, invoke when called |
| `name(input: value)` | Invoke imported program with inputs, receive outputs | | `name(input: value)` | Invoke imported program with inputs, receive outputs |
You are the container that holds these declarations and wires them together at runtime. The program declares _what_; you determine _how_ to connect them. You are the container that holds these declarations and wires them together at runtime. The program declares _what_; you determine _how_ to connect them.

View File

@@ -104,12 +104,13 @@ The execution state file shows the program's current position using **annotated
**Only the VM writes this file.** Subagents never modify `state.md`. **Only the VM writes this file.** Subagents never modify `state.md`.
The format shows: The format shows:
- **Full history** of executed code with inline annotations - **Full history** of executed code with inline annotations
- **Current position** clearly marked with status - **Current position** clearly marked with status
- **~5-10 lines ahead** of current position (what's coming next) - **~5-10 lines ahead** of current position (what's coming next)
- **Index** of all bindings and agents with file paths - **Index** of all bindings and agents with file paths
```markdown ````markdown
# Execution State # Execution State
run: 20260115-143052-a7b3c9 run: 20260115-143052-a7b3c9
@@ -144,6 +145,7 @@ resume: captain # [...next...]
prompt: "Review the synthesis" prompt: "Review the synthesis"
context: synthesis context: synthesis
``` ```
````
## Active Constructs ## Active Constructs
@@ -162,26 +164,27 @@ resume: captain # [...next...]
### Bindings ### Bindings
| Name | Kind | Path | Execution ID | | Name | Kind | Path | Execution ID |
|------|------|------|--------------| | -------- | ---- | ------------------------ | ------------ |
| research | let | bindings/research.md | (root) | | research | let | bindings/research.md | (root) |
| a | let | bindings/a.md | (root) | | a | let | bindings/a.md | (root) |
| result | let | bindings/result__43.md | 43 | | result | let | bindings/result\_\_43.md | 43 |
### Agents ### Agents
| Name | Scope | Path | | Name | Scope | Path |
|------|-------|------| | ------- | --------- | --------------- |
| captain | execution | agents/captain/ | | captain | execution | agents/captain/ |
## Call Stack ## Call Stack
| execution_id | block | depth | status | | execution_id | block | depth | status |
|--------------|-------|-------|--------| | ------------ | ------- | ----- | --------- |
| 43 | process | 3 | executing | | 43 | process | 3 | executing |
| 42 | process | 2 | waiting | | 42 | process | 2 | waiting |
| 41 | process | 1 | waiting | | 41 | process | 1 | waiting |
```
````
**Status annotations:** **Status annotations:**
@@ -209,14 +212,15 @@ source:
```prose ```prose
let research = session: researcher let research = session: researcher
prompt: "Research AI safety" prompt: "Research AI safety"
``` ````
--- ---
AI safety research covers several key areas including alignment, AI safety research covers several key areas including alignment,
robustness, and interpretability. The field has grown significantly robustness, and interpretability. The field has grown significantly
since 2020 with major contributions from... since 2020 with major contributions from...
```
````
**Structure:** **Structure:**
- Header with binding name - Header with binding name
@@ -240,7 +244,7 @@ Sessions without explicit output capture still produce results:
```prose ```prose
session "Analyze the codebase" # No `let x = ...` capture session "Analyze the codebase" # No `let x = ...` capture
``` ````
These get auto-generated names with an `anon_` prefix: These get auto-generated names with an `anon_` prefix:
@@ -259,25 +263,29 @@ When a binding is created inside a block invocation, it's scoped to that executi
**Naming convention:** `{name}__{execution_id}.md` **Naming convention:** `{name}__{execution_id}.md`
Examples: Examples:
- `bindings/result__43.md` — binding `result` in execution_id 43 - `bindings/result__43.md` — binding `result` in execution_id 43
- `bindings/parts__44.md` — binding `parts` in execution_id 44 - `bindings/parts__44.md` — binding `parts` in execution_id 44
**File format with execution scope:** **File format with execution scope:**
```markdown ````markdown
# result # result
kind: let kind: let
execution_id: 43 execution_id: 43
source: source:
```prose ```prose
let result = session "Process chunk" let result = session "Process chunk"
``` ```
````
--- ---
Processed chunk into 3 sub-parts... Processed chunk into 3 sub-parts...
``` ```
**Scope resolution:** The VM resolves variable references by checking: **Scope resolution:** The VM resolves variable references by checking:
@@ -291,15 +299,17 @@ The first match wins.
**Example directory for recursive calls:** **Example directory for recursive calls:**
``` ```
bindings/ bindings/
├── data.md # Root scope input ├── data.md # Root scope input
├── result__1.md # First process() invocation ├── result**1.md # First process() invocation
├── parts__1.md # Parts from first invocation ├── parts**1.md # Parts from first invocation
├── result__2.md # Recursive call (depth 2) ├── result**2.md # Recursive call (depth 2)
├── parts__2.md # Parts from depth 2 ├── parts**2.md # Parts from depth 2
├── result__3.md # Recursive call (depth 3) ├── result\_\_3.md # Recursive call (depth 3)
└── ... └── ...
```
````
--- ---
@@ -326,7 +336,7 @@ Architecture uses Express + PostgreSQL. Test coverage target is 80%.
- Rate limiting not yet implemented on login endpoint - Rate limiting not yet implemented on login endpoint
- Need to verify OAuth flow works with new token format - Need to verify OAuth flow works with new token format
``` ````
#### `agents/{name}/{name}-NNN.md` (Segments) #### `agents/{name}/{name}-NNN.md` (Segments)
@@ -350,11 +360,11 @@ prompt: "Review the research findings"
## Who Writes What ## Who Writes What
| File | Written By | | File | Written By |
|------|------------| | ----------------------------- | ---------------- |
| `state.md` | VM only | | `state.md` | VM only |
| `bindings/{name}.md` | Subagent | | `bindings/{name}.md` | Subagent |
| `agents/{name}/memory.md` | Persistent agent | | `agents/{name}/memory.md` | Persistent agent |
| `agents/{name}/{name}-NNN.md` | Persistent agent | | `agents/{name}/{name}-NNN.md` | Persistent agent |
The VM orchestrates; subagents write their own outputs directly to the filesystem. **The VM never holds full binding values—it tracks file paths.** The VM orchestrates; subagents write their own outputs directly to the filesystem. **The VM never holds full binding values—it tracks file paths.**
@@ -367,7 +377,7 @@ When the VM spawns a session, it tells the subagent where to write output.
### For Regular Sessions ### For Regular Sessions
``` ````
When you complete this task, write your output to: When you complete this task, write your output to:
.prose/runs/20260115-143052-a7b3c9/bindings/research.md .prose/runs/20260115-143052-a7b3c9/bindings/research.md
@@ -380,24 +390,27 @@ source:
```prose ```prose
let research = session: researcher let research = session: researcher
prompt: "Research AI safety" prompt: "Research AI safety"
``` ````
--- ---
[Your output here] [Your output here]
``` ```
### For Persistent Agents (resume:) ### For Persistent Agents (resume:)
``` ```
Your memory is at: Your memory is at:
.prose/runs/20260115-143052-a7b3c9/agents/captain/memory.md .prose/runs/20260115-143052-a7b3c9/agents/captain/memory.md
Read it first to understand your prior context. When done, update it Read it first to understand your prior context. When done, update it
with your compacted state following the guidelines in primitives/session.md. with your compacted state following the guidelines in primitives/session.md.
Also write your segment record to: Also write your segment record to:
.prose/runs/20260115-143052-a7b3c9/agents/captain/captain-003.md .prose/runs/20260115-143052-a7b3c9/agents/captain/captain-003.md
``` ```
### What Subagents Return to the VM ### What Subagents Return to the VM
@@ -406,17 +419,21 @@ After writing output, the subagent returns a **confirmation message**—not the
**Root scope (outside block invocations):** **Root scope (outside block invocations):**
``` ```
Binding written: research Binding written: research
Location: .prose/runs/20260115-143052-a7b3c9/bindings/research.md Location: .prose/runs/20260115-143052-a7b3c9/bindings/research.md
Summary: AI safety research covering alignment, robustness, and interpretability with 15 citations. Summary: AI safety research covering alignment, robustness, and interpretability with 15 citations.
``` ```
**Inside block invocation (include execution_id):** **Inside block invocation (include execution_id):**
``` ```
Binding written: result Binding written: result
Location: .prose/runs/20260115-143052-a7b3c9/bindings/result__43.md Location: .prose/runs/20260115-143052-a7b3c9/bindings/result\_\_43.md
Execution ID: 43 Execution ID: 43
Summary: Processed chunk into 3 sub-parts for recursive processing. Summary: Processed chunk into 3 sub-parts for recursive processing.
``` ```
The VM records the location and continues. It does NOT read the file—it passes the reference to subsequent sessions that need the context. The VM records the location and continues. It does NOT read the file—it passes the reference to subsequent sessions that need the context.
@@ -428,16 +445,18 @@ The VM records the location and continues. It does NOT read the file—it passes
Imported programs use the **same unified structure recursively**: Imported programs use the **same unified structure recursively**:
``` ```
.prose/runs/{id}/imports/{handle}--{slug}/ .prose/runs/{id}/imports/{handle}--{slug}/
├── program.prose ├── program.prose
├── state.md ├── state.md
├── bindings/ ├── bindings/
└── {name}.md │ └── {name}.md
├── imports/ # Nested imports go here ├── imports/ # Nested imports go here
└── {handle2}--{slug2}/ │ └── {handle2}--{slug2}/
└── ... │ └── ...
└── agents/ └── agents/
└── {name}/ └── {name}/
``` ```
This allows unlimited nesting depth while maintaining consistent structure at every level. This allows unlimited nesting depth while maintaining consistent structure at every level.
@@ -476,3 +495,4 @@ If execution is interrupted, resume by:
3. Continuing from the marked position 3. Continuing from the marked position
The `state.md` file contains everything needed to understand where execution stopped and what has been accomplished. The `state.md` file contains everything needed to understand where execution stopped and what has been accomplished.
```

View File

@@ -28,13 +28,13 @@ In-context state uses text-prefixed markers to persist state within the conversa
In-context state is appropriate for: In-context state is appropriate for:
| Factor | In-Context | Use File-Based Instead | | Factor | In-Context | Use File-Based Instead |
|--------|------------|------------------------| | ----------------- | --------------- | ---------------------- |
| Statement count | < 30 statements | >= 30 statements | | Statement count | < 30 statements | >= 30 statements |
| Parallel branches | < 5 concurrent | >= 5 concurrent | | Parallel branches | < 5 concurrent | >= 5 concurrent |
| Imported programs | 0-2 imports | >= 3 imports | | Imported programs | 0-2 imports | >= 3 imports |
| Nested depth | <= 2 levels | > 2 levels | | Nested depth | <= 2 levels | > 2 levels |
| Expected duration | < 5 minutes | >= 5 minutes | | Expected duration | < 5 minutes | >= 5 minutes |
Announce your state mode at program start: Announce your state mode at program start:
@@ -49,23 +49,23 @@ OpenProse Program Start
Use text-prefixed markers for each state change: Use text-prefixed markers for each state change:
| Marker | Category | Usage | | Marker | Category | Usage |
|--------|----------|-------| | ---------- | -------------- | --------------------------------------- |
| [Program] | Program | Start, end, definition collection | | [Program] | Program | Start, end, definition collection |
| [Position] | Position | Current statement being executed | | [Position] | Position | Current statement being executed |
| [Binding] | Binding | Variable assignment or update | | [Binding] | Binding | Variable assignment or update |
| [Input] | Input | Receiving inputs from caller | | [Input] | Input | Receiving inputs from caller |
| [Output] | Output | Producing outputs for caller | | [Output] | Output | Producing outputs for caller |
| [Import] | Import | Fetching and invoking imported programs | | [Import] | Import | Fetching and invoking imported programs |
| [Success] | Success | Session or block completion | | [Success] | Success | Session or block completion |
| [Warning] | Error | Failures and exceptions | | [Warning] | Error | Failures and exceptions |
| [Parallel] | Parallel | Entering, branch status, joining | | [Parallel] | Parallel | Entering, branch status, joining |
| [Loop] | Loop | Iteration, condition evaluation | | [Loop] | Loop | Iteration, condition evaluation |
| [Pipeline] | Pipeline | Stage progress | | [Pipeline] | Pipeline | Stage progress |
| [Try] | Error handling | Try/catch/finally | | [Try] | Error handling | Try/catch/finally |
| [Flow] | Flow | Condition evaluation results | | [Flow] | Flow | Condition evaluation results |
| [Frame+] | Call Stack | Push new frame (block invocation) | | [Frame+] | Call Stack | Push new frame (block invocation) |
| [Frame-] | Call Stack | Pop frame (block completion) | | [Frame-] | Call Stack | Pop frame (block completion) |
--- ---
@@ -185,6 +185,7 @@ Track block invocations with frame markers:
``` ```
**Key points:** **Key points:**
- Each `[Frame+]` must have a matching `[Frame-]` - Each `[Frame+]` must have a matching `[Frame-]`
- `execution_id` uniquely identifies each invocation - `execution_id` uniquely identifies each invocation
- `depth` shows call stack depth (1 = first level) - `depth` shows call stack depth (1 = first level)
@@ -236,13 +237,14 @@ For variable resolution across scopes:
When passing context to sessions, format appropriately: When passing context to sessions, format appropriately:
| Context Size | Strategy | | Context Size | Strategy |
|--------------|----------| | --------------- | ----------------------- |
| < 2000 chars | Pass verbatim | | < 2000 chars | Pass verbatim |
| 2000-8000 chars | Summarize to key points | | 2000-8000 chars | Summarize to key points |
| > 8000 chars | Extract essentials only | | > 8000 chars | Extract essentials only |
**Format:** **Format:**
``` ```
Context provided: Context provided:
--- ---
@@ -274,6 +276,7 @@ loop until **analysis complete** (max: 3):
``` ```
**Narration:** **Narration:**
``` ```
[Program] Program Start [Program] Program Start
Collecting definitions... Collecting definitions...
@@ -322,20 +325,20 @@ loop until **analysis complete** (max: 3):
The VM must track these state categories in narration: The VM must track these state categories in narration:
| Category | What to Track | Example | | Category | What to Track | Example |
|----------|---------------|---------| | ----------------------- | ----------------------------------------- | -------------------------------------------- |
| **Import Registry** | Imported programs and aliases | `research: @alice/research` | | **Import Registry** | Imported programs and aliases | `research: @alice/research` |
| **Agent Registry** | All agent definitions | `researcher: {model: sonnet, prompt: "..."}` | | **Agent Registry** | All agent definitions | `researcher: {model: sonnet, prompt: "..."}` |
| **Block Registry** | All block definitions (hoisted) | `review: {params: [topic], body: [...]}` | | **Block Registry** | All block definitions (hoisted) | `review: {params: [topic], body: [...]}` |
| **Input Bindings** | Inputs received from caller | `topic = "quantum computing"` | | **Input Bindings** | Inputs received from caller | `topic = "quantum computing"` |
| **Output Bindings** | Outputs to return to caller | `findings = "Research shows..."` | | **Output Bindings** | Outputs to return to caller | `findings = "Research shows..."` |
| **Variable Bindings** | Name -> value mapping (with execution_id) | `result = "..." (execution_id: 3)` | | **Variable Bindings** | Name -> value mapping (with execution_id) | `result = "..." (execution_id: 3)` |
| **Variable Mutability** | Which are `let` vs `const` vs `output` | `research: let, findings: output` | | **Variable Mutability** | Which are `let` vs `const` vs `output` | `research: let, findings: output` |
| **Execution Position** | Current statement index | Statement 3 of 7 | | **Execution Position** | Current statement index | Statement 3 of 7 |
| **Loop State** | Counter, max, condition | Iteration 2 of max 5 | | **Loop State** | Counter, max, condition | Iteration 2 of max 5 |
| **Parallel State** | Branches, results, strategy | `{a: complete, b: pending}` | | **Parallel State** | Branches, results, strategy | `{a: complete, b: pending}` |
| **Error State** | Exception, retry count | Retry 2 of 3, error: "timeout" | | **Error State** | Exception, retry count | Retry 2 of 3, error: "timeout" |
| **Call Stack** | Stack of execution frames | See below | | **Call Stack** | Stack of execution frames | See below |
### Call Stack State ### Call Stack State
@@ -349,6 +352,7 @@ For block invocations, track the full call stack:
``` ```
Each frame tracks: Each frame tracks:
- `execution_id`: Unique ID for this invocation - `execution_id`: Unique ID for this invocation
- `block`: Name of the block - `block`: Name of the block
- `depth`: Position in call stack - `depth`: Position in call stack

View File

@@ -21,19 +21,20 @@ This document describes how the OpenProse VM tracks execution state using a **Po
## Prerequisites ## Prerequisites
**Requires:** **Requires:**
1. The `psql` command-line tool must be available in your PATH 1. The `psql` command-line tool must be available in your PATH
2. A running PostgreSQL server (local, Docker, or cloud) 2. A running PostgreSQL server (local, Docker, or cloud)
### Installing psql ### Installing psql
| Platform | Command | Notes | | Platform | Command | Notes |
|----------|---------|-------| | -------------------- | ----------------------------------------------- | ---------------------- |
| macOS (Homebrew) | `brew install libpq && brew link --force libpq` | Client-only; no server | | macOS (Homebrew) | `brew install libpq && brew link --force libpq` | Client-only; no server |
| macOS (Postgres.app) | Download from https://postgresapp.com | Full install with GUI | | macOS (Postgres.app) | Download from https://postgresapp.com | Full install with GUI |
| Debian/Ubuntu | `apt install postgresql-client` | Client-only | | Debian/Ubuntu | `apt install postgresql-client` | Client-only |
| Fedora/RHEL | `dnf install postgresql` | Client-only | | Fedora/RHEL | `dnf install postgresql` | Client-only |
| Arch Linux | `pacman -S postgresql-libs` | Client-only | | Arch Linux | `pacman -S postgresql-libs` | Client-only |
| Windows | `winget install PostgreSQL.PostgreSQL` | Full installer | | Windows | `winget install PostgreSQL.PostgreSQL` | Full installer |
After installation, verify: After installation, verify:
@@ -70,6 +71,7 @@ PostgreSQL state provides:
- Create a **limited-privilege user** with access only to the `openprose` schema - Create a **limited-privilege user** with access only to the `openprose` schema
**Recommended setup:** **Recommended setup:**
```sql ```sql
-- Create dedicated user with minimal privileges -- Create dedicated user with minimal privileges
CREATE USER openprose_agent WITH PASSWORD 'changeme'; CREATE USER openprose_agent WITH PASSWORD 'changeme';
@@ -84,13 +86,13 @@ GRANT ALL ON SCHEMA openprose TO openprose_agent;
PostgreSQL state is for **power users** with specific scale or collaboration needs: PostgreSQL state is for **power users** with specific scale or collaboration needs:
| Need | PostgreSQL Helps | | Need | PostgreSQL Helps |
|------|------------------| | ------------------------------------------- | --------------------------------------------- |
| >5 parallel branches writing simultaneously | SQLite locks; PostgreSQL doesn't | | >5 parallel branches writing simultaneously | SQLite locks; PostgreSQL doesn't |
| External dashboards querying state | PostgreSQL is designed for concurrent readers | | External dashboards querying state | PostgreSQL is designed for concurrent readers |
| Team collaboration on long workflows | Shared network access; no file sync needed | | Team collaboration on long workflows | Shared network access; no file sync needed |
| Outputs exceeding 1GB | Bulk ingestion; no single-file bottleneck | | Outputs exceeding 1GB | Bulk ingestion; no single-file bottleneck |
| Mission-critical workflows (hours/days) | Robust durability; point-in-time recovery | | Mission-critical workflows (hours/days) | Robust durability; point-in-time recovery |
**If none of these apply, use filesystem or SQLite state.** They're simpler and sufficient for 99% of programs. **If none of these apply, use filesystem or SQLite state.** They're simpler and sufficient for 99% of programs.
@@ -187,11 +189,11 @@ echo "OPENPROSE_POSTGRES_URL=postgresql:///myproject" >> .prose/.env
For team collaboration or production: For team collaboration or production:
| Provider | Free Tier | Cold Start | Best For | | Provider | Free Tier | Cold Start | Best For |
|----------|-----------|------------|----------| | ------------ | ---------------------- | ---------- | ----------------------------- |
| **Neon** | 0.5GB, auto-suspend | 1-3s | Development, testing | | **Neon** | 0.5GB, auto-suspend | 1-3s | Development, testing |
| **Supabase** | 500MB, no auto-suspend | None | Projects needing auth/storage | | **Supabase** | 500MB, no auto-suspend | None | Projects needing auth/storage |
| **Railway** | $5/mo credit | None | Simple production deploys | | **Railway** | $5/mo credit | None | Simple production deploys |
```bash ```bash
# Example: Neon # Example: Neon
@@ -246,17 +248,17 @@ This section defines **who does what**. This is the contract between the VM and
The VM (the orchestrating agent running the .prose program) is responsible for: The VM (the orchestrating agent running the .prose program) is responsible for:
| Responsibility | Description | | Responsibility | Description |
|----------------|-------------| | ------------------------- | --------------------------------------------------------- |
| **Schema initialization** | Create `openprose` schema and tables at run start | | **Schema initialization** | Create `openprose` schema and tables at run start |
| **Run registration** | Store the program source and metadata | | **Run registration** | Store the program source and metadata |
| **Execution tracking** | Update position, status, and timing as statements execute | | **Execution tracking** | Update position, status, and timing as statements execute |
| **Subagent spawning** | Spawn sessions via Task tool with database instructions | | **Subagent spawning** | Spawn sessions via Task tool with database instructions |
| **Parallel coordination** | Track branch status, implement join strategies | | **Parallel coordination** | Track branch status, implement join strategies |
| **Loop management** | Track iteration counts, evaluate conditions | | **Loop management** | Track iteration counts, evaluate conditions |
| **Error aggregation** | Record failures, manage retry state | | **Error aggregation** | Record failures, manage retry state |
| **Context preservation** | Maintain sufficient narration in the main thread | | **Context preservation** | Maintain sufficient narration in the main thread |
| **Completion detection** | Mark the run as complete when finished | | **Completion detection** | Mark the run as complete when finished |
**Critical:** The VM must preserve enough context in its own conversation to understand execution state without re-reading the entire database. The database is for coordination and persistence, not a replacement for working memory. **Critical:** The VM must preserve enough context in its own conversation to understand execution state without re-reading the entire database. The database is for coordination and persistence, not a replacement for working memory.
@@ -264,13 +266,13 @@ The VM (the orchestrating agent running the .prose program) is responsible for:
Subagents (sessions spawned by the VM) are responsible for: Subagents (sessions spawned by the VM) are responsible for:
| Responsibility | Description | | Responsibility | Description |
|----------------|-------------| | ----------------------- | ----------------------------------------------------------------- |
| **Writing own outputs** | Insert/update their binding in the `bindings` table | | **Writing own outputs** | Insert/update their binding in the `bindings` table |
| **Memory management** | For persistent agents: read and update their memory record | | **Memory management** | For persistent agents: read and update their memory record |
| **Segment recording** | For persistent agents: append segment history | | **Segment recording** | For persistent agents: append segment history |
| **Attachment handling** | Write large outputs to `attachments/` directory, store path in DB | | **Attachment handling** | Write large outputs to `attachments/` directory, store path in DB |
| **Atomic writes** | Use transactions when updating multiple related records | | **Atomic writes** | Use transactions when updating multiple related records |
**Critical:** Subagents write ONLY to `bindings`, `agents`, and `agent_segments` tables. The VM owns the `execution` table entirely. Completion signaling happens through the substrate (Task tool return), not database updates. **Critical:** Subagents write ONLY to `bindings`, `agents`, and `agent_segments` tables. The VM owns the `execution` table entirely. Completion signaling happens through the substrate (Task tool return), not database updates.
@@ -279,6 +281,7 @@ Subagents (sessions spawned by the VM) are responsible for:
**What subagents return to the VM:** A confirmation message with the binding location—not the full content: **What subagents return to the VM:** A confirmation message with the binding location—not the full content:
**Root scope:** **Root scope:**
``` ```
Binding written: research Binding written: research
Location: openprose.bindings WHERE name='research' AND run_id='20260116-143052-a7b3c9' AND execution_id IS NULL Location: openprose.bindings WHERE name='research' AND run_id='20260116-143052-a7b3c9' AND execution_id IS NULL
@@ -286,6 +289,7 @@ Summary: AI safety research covering alignment, robustness, and interpretability
``` ```
**Inside block invocation:** **Inside block invocation:**
``` ```
Binding written: result Binding written: result
Location: openprose.bindings WHERE name='result' AND run_id='20260116-143052-a7b3c9' AND execution_id=43 Location: openprose.bindings WHERE name='result' AND run_id='20260116-143052-a7b3c9' AND execution_id=43
@@ -297,12 +301,12 @@ The VM tracks locations, not values. This keeps the VM's context lean and enable
### Shared Concerns ### Shared Concerns
| Concern | Who Handles | | Concern | Who Handles |
|---------|-------------| | ---------------- | ------------------------------------------------------------------ |
| Schema evolution | Either (use `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE` as needed) | | Schema evolution | Either (use `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE` as needed) |
| Custom tables | Either (prefix with `x_` for extensions) | | Custom tables | Either (prefix with `x_` for extensions) |
| Indexing | Either (add indexes for frequently-queried columns) | | Indexing | Either (add indexes for frequently-queried columns) |
| Cleanup | VM (at run end, optionally delete old data) | | Cleanup | VM (at run end, optionally delete old data) |
--- ---
@@ -601,12 +605,12 @@ Even with PostgreSQL state, the VM should narrate key events in its conversation
### Why Both? ### Why Both?
| Purpose | Mechanism | | Purpose | Mechanism |
|---------|-----------| | ------------------------- | -------------------------------------------------------------------- |
| **Working memory** | Conversation narration (what the VM "remembers" without re-querying) | | **Working memory** | Conversation narration (what the VM "remembers" without re-querying) |
| **Durable state** | PostgreSQL database (survives context limits, enables resumption) | | **Durable state** | PostgreSQL database (survives context limits, enables resumption) |
| **Subagent coordination** | PostgreSQL database (shared access point) | | **Subagent coordination** | PostgreSQL database (shared access point) |
| **Debugging/inspection** | PostgreSQL database (queryable history) | | **Debugging/inspection** | PostgreSQL database (queryable history) |
The narration is the VM's "mental model" of execution. The database is the "source of truth" for resumption and inspection. The narration is the VM's "mental model" of execution. The database is the "source of truth" for resumption and inspection.
@@ -717,6 +721,7 @@ PRIMARY KEY (name, COALESCE(run_id, '__project__'))
``` ```
This means: This means:
- `name='advisor', run_id=NULL` has PK `('advisor', '__project__')` - `name='advisor', run_id=NULL` has PK `('advisor', '__project__')`
- `name='advisor', run_id='20260116-143052-a7b3c9'` has PK `('advisor', '20260116-143052-a7b3c9')` - `name='advisor', run_id='20260116-143052-a7b3c9'` has PK `('advisor', '20260116-143052-a7b3c9')`
@@ -724,10 +729,10 @@ The same agent name can exist as both project-scoped and execution-scoped withou
### Query Patterns ### Query Patterns
| Scope | Query | | Scope | Query |
|-------|-------| | ---------------- | ------------------------------------------------ |
| Execution-scoped | `WHERE name = 'captain' AND run_id = '{RUN_ID}'` | | Execution-scoped | `WHERE name = 'captain' AND run_id = '{RUN_ID}'` |
| Project-scoped | `WHERE name = 'advisor' AND run_id IS NULL` | | Project-scoped | `WHERE name = 'advisor' AND run_id IS NULL` |
### Project-Scoped Memory Guidelines ### Project-Scoped Memory Guidelines
@@ -841,20 +846,20 @@ The database is your workspace. Use it.
## Comparison with Other Modes ## Comparison with Other Modes
| Aspect | filesystem.md | in-context.md | sqlite.md | postgres.md | | Aspect | filesystem.md | in-context.md | sqlite.md | postgres.md |
|--------|---------------|---------------|-----------|-------------| | ---------------------- | ------------------------- | -------------------- | --------------------------- | --------------------- |
| **State location** | `.prose/runs/{id}/` files | Conversation history | `.prose/runs/{id}/state.db` | PostgreSQL database | | **State location** | `.prose/runs/{id}/` files | Conversation history | `.prose/runs/{id}/state.db` | PostgreSQL database |
| **Queryable** | Via file reads | No | Yes (SQL) | Yes (SQL) | | **Queryable** | Via file reads | No | Yes (SQL) | Yes (SQL) |
| **Atomic updates** | No | N/A | Yes (transactions) | Yes (ACID) | | **Atomic updates** | No | N/A | Yes (transactions) | Yes (ACID) |
| **Concurrent writes** | Yes (different files) | N/A | **No (table locks)** | **Yes (row locks)** | | **Concurrent writes** | Yes (different files) | N/A | **No (table locks)** | **Yes (row locks)** |
| **Network access** | No | No | No | **Yes** | | **Network access** | No | No | No | **Yes** |
| **Team collaboration** | Via file sync | No | Via file sync | **Yes** | | **Team collaboration** | Via file sync | No | Via file sync | **Yes** |
| **Schema flexibility** | Rigid file structure | N/A | Flexible | Very flexible (JSONB) | | **Schema flexibility** | Rigid file structure | N/A | Flexible | Very flexible (JSONB) |
| **Resumption** | Read state.md | Re-read conversation | Query database | Query database | | **Resumption** | Read state.md | Re-read conversation | Query database | Query database |
| **Complexity ceiling** | High | Low (<30 statements) | High | **Very high** | | **Complexity ceiling** | High | Low (<30 statements) | High | **Very high** |
| **Dependency** | None | None | sqlite3 CLI | psql CLI + PostgreSQL | | **Dependency** | None | None | sqlite3 CLI | psql CLI + PostgreSQL |
| **Setup friction** | Zero | Zero | Low | Medium-High | | **Setup friction** | Zero | Zero | Low | Medium-High |
| **Status** | Stable | Stable | Experimental | **Experimental** | | **Status** | Stable | Stable | Experimental | **Experimental** |
--- ---

View File

@@ -21,11 +21,11 @@ This document describes how the OpenProse VM tracks execution state using a **SQ
**Requires:** The `sqlite3` command-line tool must be available in your PATH. **Requires:** The `sqlite3` command-line tool must be available in your PATH.
| Platform | Installation | | Platform | Installation |
|----------|--------------| | -------- | ---------------------------------------------------------- |
| macOS | Pre-installed | | macOS | Pre-installed |
| Linux | `apt install sqlite3` / `dnf install sqlite3` / etc. | | Linux | `apt install sqlite3` / `dnf install sqlite3` / etc. |
| Windows | `winget install SQLite.SQLite` or download from sqlite.org | | Windows | `winget install SQLite.SQLite` or download from sqlite.org |
If `sqlite3` is not available, the VM will fall back to filesystem state and warn the user. If `sqlite3` is not available, the VM will fall back to filesystem state and warn the user.
@@ -93,17 +93,17 @@ This section defines **who does what**. This is the contract between the VM and
The VM (the orchestrating agent running the .prose program) is responsible for: The VM (the orchestrating agent running the .prose program) is responsible for:
| Responsibility | Description | | Responsibility | Description |
|----------------|-------------| | ------------------------- | -------------------------------------------------------------------------------------------------------- |
| **Database creation** | Create `state.db` and initialize core tables at run start | | **Database creation** | Create `state.db` and initialize core tables at run start |
| **Program registration** | Store the program source and metadata | | **Program registration** | Store the program source and metadata |
| **Execution tracking** | Update position, status, and timing as statements execute | | **Execution tracking** | Update position, status, and timing as statements execute |
| **Subagent spawning** | Spawn sessions via Task tool with database path and instructions | | **Subagent spawning** | Spawn sessions via Task tool with database path and instructions |
| **Parallel coordination** | Track branch status, implement join strategies | | **Parallel coordination** | Track branch status, implement join strategies |
| **Loop management** | Track iteration counts, evaluate conditions | | **Loop management** | Track iteration counts, evaluate conditions |
| **Error aggregation** | Record failures, manage retry state | | **Error aggregation** | Record failures, manage retry state |
| **Context preservation** | Maintain sufficient narration in the main conversation thread so execution can be understood and resumed | | **Context preservation** | Maintain sufficient narration in the main conversation thread so execution can be understood and resumed |
| **Completion detection** | Mark the run as complete when finished | | **Completion detection** | Mark the run as complete when finished |
**Critical:** The VM must preserve enough context in its own conversation to understand execution state without re-reading the entire database. The database is for coordination and persistence, not a replacement for working memory. **Critical:** The VM must preserve enough context in its own conversation to understand execution state without re-reading the entire database. The database is for coordination and persistence, not a replacement for working memory.
@@ -111,13 +111,13 @@ The VM (the orchestrating agent running the .prose program) is responsible for:
Subagents (sessions spawned by the VM) are responsible for: Subagents (sessions spawned by the VM) are responsible for:
| Responsibility | Description | | Responsibility | Description |
|----------------|-------------| | ----------------------- | ----------------------------------------------------------------- |
| **Writing own outputs** | Insert/update their binding in the `bindings` table | | **Writing own outputs** | Insert/update their binding in the `bindings` table |
| **Memory management** | For persistent agents: read and update their memory record | | **Memory management** | For persistent agents: read and update their memory record |
| **Segment recording** | For persistent agents: append segment history | | **Segment recording** | For persistent agents: append segment history |
| **Attachment handling** | Write large outputs to `attachments/` directory, store path in DB | | **Attachment handling** | Write large outputs to `attachments/` directory, store path in DB |
| **Atomic writes** | Use transactions when updating multiple related records | | **Atomic writes** | Use transactions when updating multiple related records |
**Critical:** Subagents write ONLY to `bindings`, `agents`, and `agent_segments` tables. The VM owns the `execution` table entirely. Completion signaling happens through the substrate (Task tool return), not database updates. **Critical:** Subagents write ONLY to `bindings`, `agents`, and `agent_segments` tables. The VM owns the `execution` table entirely. Completion signaling happens through the substrate (Task tool return), not database updates.
@@ -126,6 +126,7 @@ Subagents (sessions spawned by the VM) are responsible for:
**What subagents return to the VM:** A confirmation message with the binding location—not the full content: **What subagents return to the VM:** A confirmation message with the binding location—not the full content:
**Root scope:** **Root scope:**
``` ```
Binding written: research Binding written: research
Location: .prose/runs/20260116-143052-a7b3c9/state.db (bindings table, name='research', execution_id=NULL) Location: .prose/runs/20260116-143052-a7b3c9/state.db (bindings table, name='research', execution_id=NULL)
@@ -133,6 +134,7 @@ Summary: AI safety research covering alignment, robustness, and interpretability
``` ```
**Inside block invocation:** **Inside block invocation:**
``` ```
Binding written: result Binding written: result
Location: .prose/runs/20260116-143052-a7b3c9/state.db (bindings table, name='result', execution_id=43) Location: .prose/runs/20260116-143052-a7b3c9/state.db (bindings table, name='result', execution_id=43)
@@ -144,12 +146,12 @@ The VM tracks locations, not values. This keeps the VM's context lean and enable
### Shared Concerns ### Shared Concerns
| Concern | Who Handles | | Concern | Who Handles |
|---------|-------------| | ---------------- | ------------------------------------------------------------------ |
| Schema evolution | Either (use `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE` as needed) | | Schema evolution | Either (use `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE` as needed) |
| Custom tables | Either (prefix with `x_` for extensions) | | Custom tables | Either (prefix with `x_` for extensions) |
| Indexing | Either (add indexes for frequently-queried columns) | | Indexing | Either (add indexes for frequently-queried columns) |
| Cleanup | VM (at run end, optionally vacuum) | | Cleanup | VM (at run end, optionally vacuum) |
--- ---
@@ -390,12 +392,12 @@ Even with SQLite state, the VM should narrate key events in its conversation:
### Why Both? ### Why Both?
| Purpose | Mechanism | | Purpose | Mechanism |
|---------|-----------| | ------------------------- | -------------------------------------------------------------------- |
| **Working memory** | Conversation narration (what the VM "remembers" without re-querying) | | **Working memory** | Conversation narration (what the VM "remembers" without re-querying) |
| **Durable state** | SQLite database (survives context limits, enables resumption) | | **Durable state** | SQLite database (survives context limits, enables resumption) |
| **Subagent coordination** | SQLite database (shared access point) | | **Subagent coordination** | SQLite database (shared access point) |
| **Debugging/inspection** | SQLite database (queryable history) | | **Debugging/inspection** | SQLite database (queryable history) |
The narration is the VM's "mental model" of execution. The database is the "source of truth" for resumption and inspection. The narration is the VM's "mental model" of execution. The database is the "source of truth" for resumption and inspection.
@@ -544,16 +546,16 @@ The database is your workspace. Use it.
## Comparison with Other Modes ## Comparison with Other Modes
| Aspect | filesystem.md | in-context.md | sqlite.md | | Aspect | filesystem.md | in-context.md | sqlite.md |
|--------|---------------|---------------|-----------| | ---------------------- | ------------------------- | -------------------- | ----------------------------- |
| **State location** | `.prose/runs/{id}/` files | Conversation history | `.prose/runs/{id}/state.db` | | **State location** | `.prose/runs/{id}/` files | Conversation history | `.prose/runs/{id}/state.db` |
| **Queryable** | Via file reads | No | Yes (SQL) | | **Queryable** | Via file reads | No | Yes (SQL) |
| **Atomic updates** | No | N/A | Yes (transactions) | | **Atomic updates** | No | N/A | Yes (transactions) |
| **Schema flexibility** | Rigid file structure | N/A | Flexible (add tables/columns) | | **Schema flexibility** | Rigid file structure | N/A | Flexible (add tables/columns) |
| **Resumption** | Read state.md | Re-read conversation | Query database | | **Resumption** | Read state.md | Re-read conversation | Query database |
| **Complexity ceiling** | High | Low (<30 statements) | High | | **Complexity ceiling** | High | Low (<30 statements) | High |
| **Dependency** | None | None | sqlite3 CLI | | **Dependency** | None | None | sqlite3 CLI |
| **Status** | Stable | Stable | **Experimental** | | **Status** | Stable | Stable | **Experimental** |
--- ---

View File

@@ -13,8 +13,7 @@ type RunResult = {
usage?: Usage; usage?: Usage;
}; };
const DEFAULT_PROMPT = const DEFAULT_PROMPT = "Reply with a single word: ok. No punctuation or extra text.";
"Reply with a single word: ok. No punctuation or extra text.";
const DEFAULT_RUNS = 10; const DEFAULT_RUNS = 10;
function parseArg(flag: string): string | undefined { function parseArg(flag: string): string | undefined {
@@ -65,9 +64,7 @@ async function runModel(opts: {
); );
const durationMs = Date.now() - started; const durationMs = Date.now() - started;
results.push({ durationMs, usage: res.usage }); results.push({ durationMs, usage: res.usage });
console.log( console.log(`${opts.label} run ${i + 1}/${opts.runs}: ${durationMs}ms`);
`${opts.label} run ${i + 1}/${opts.runs}: ${durationMs}ms`,
);
} }
return results; return results;
} }
@@ -85,10 +82,8 @@ async function main(): Promise<void> {
throw new Error("Missing MINIMAX_API_KEY in environment."); throw new Error("Missing MINIMAX_API_KEY in environment.");
} }
const minimaxBaseUrl = const minimaxBaseUrl = process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1"; const minimaxModelId = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1";
const minimaxModelId =
process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1";
const minimaxModel: Model<"openai-completions"> = { const minimaxModel: Model<"openai-completions"> = {
id: minimaxModelId, id: minimaxModelId,
@@ -135,9 +130,7 @@ async function main(): Promise<void> {
console.log(""); console.log("");
console.log("Summary (ms):"); console.log("Summary (ms):");
for (const row of summary) { for (const row of summary) {
console.log( console.log(`${row.label.padEnd(7)} median=${row.med} min=${row.min} max=${row.max}`);
`${row.label.padEnd(7)} median=${row.med} min=${row.min} max=${row.max}`,
);
} }
} }

View File

@@ -5,27 +5,18 @@ import { fileURLToPath, pathToFileURL } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
export function getA2uiPaths(env = process.env) { export function getA2uiPaths(env = process.env) {
const srcDir = const srcDir = env.OPENCLAW_A2UI_SRC_DIR ?? path.join(repoRoot, "src", "canvas-host", "a2ui");
env.OPENCLAW_A2UI_SRC_DIR ?? path.join(repoRoot, "src", "canvas-host", "a2ui"); const outDir = env.OPENCLAW_A2UI_OUT_DIR ?? path.join(repoRoot, "dist", "canvas-host", "a2ui");
const outDir =
env.OPENCLAW_A2UI_OUT_DIR ?? path.join(repoRoot, "dist", "canvas-host", "a2ui");
return { srcDir, outDir }; return { srcDir, outDir };
} }
export async function copyA2uiAssets({ export async function copyA2uiAssets({ srcDir, outDir }: { srcDir: string; outDir: string }) {
srcDir,
outDir,
}: {
srcDir: string;
outDir: string;
}) {
const skipMissing = process.env.OPENCLAW_A2UI_SKIP_MISSING === "1"; const skipMissing = process.env.OPENCLAW_A2UI_SKIP_MISSING === "1";
try { try {
await fs.stat(path.join(srcDir, "index.html")); await fs.stat(path.join(srcDir, "index.html"));
await fs.stat(path.join(srcDir, "a2ui.bundle.js")); await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
} catch (err) { } catch (err) {
const message = const message = 'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
if (skipMissing) { if (skipMissing) {
console.warn(`${message} Skipping copy (OPENCLAW_A2UI_SKIP_MISSING=1).`); console.warn(`${message} Skipping copy (OPENCLAW_A2UI_SKIP_MISSING=1).`);
return; return;

View File

@@ -3,19 +3,19 @@
* Copy HOOK.md files from src/hooks/bundled to dist/hooks/bundled * Copy HOOK.md files from src/hooks/bundled to dist/hooks/bundled
*/ */
import fs from 'node:fs'; import fs from "node:fs";
import path from 'node:path'; import path from "node:path";
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, '..'); const projectRoot = path.resolve(__dirname, "..");
const srcBundled = path.join(projectRoot, 'src', 'hooks', 'bundled'); const srcBundled = path.join(projectRoot, "src", "hooks", "bundled");
const distBundled = path.join(projectRoot, 'dist', 'hooks', 'bundled'); const distBundled = path.join(projectRoot, "dist", "hooks", "bundled");
function copyHookMetadata() { function copyHookMetadata() {
if (!fs.existsSync(srcBundled)) { if (!fs.existsSync(srcBundled)) {
console.warn('[copy-hook-metadata] Source directory not found:', srcBundled); console.warn("[copy-hook-metadata] Source directory not found:", srcBundled);
return; return;
} }
@@ -31,8 +31,8 @@ function copyHookMetadata() {
const hookName = entry.name; const hookName = entry.name;
const srcHookDir = path.join(srcBundled, hookName); const srcHookDir = path.join(srcBundled, hookName);
const distHookDir = path.join(distBundled, hookName); const distHookDir = path.join(distBundled, hookName);
const srcHookMd = path.join(srcHookDir, 'HOOK.md'); const srcHookMd = path.join(srcHookDir, "HOOK.md");
const distHookMd = path.join(distHookDir, 'HOOK.md'); const distHookMd = path.join(distHookDir, "HOOK.md");
if (!fs.existsSync(srcHookMd)) { if (!fs.existsSync(srcHookMd)) {
console.warn(`[copy-hook-metadata] No HOOK.md found for ${hookName}`); console.warn(`[copy-hook-metadata] No HOOK.md found for ${hookName}`);
@@ -47,7 +47,7 @@ function copyHookMetadata() {
console.log(`[copy-hook-metadata] Copied ${hookName}/HOOK.md`); console.log(`[copy-hook-metadata] Copied ${hookName}/HOOK.md`);
} }
console.log('[copy-hook-metadata] Done'); console.log("[copy-hook-metadata] Done");
} }
copyHookMetadata(); copyHookMetadata();

View File

@@ -44,9 +44,9 @@ const parseArgs = (): Args => {
const loadAuthProfiles = (agentId: string) => { const loadAuthProfiles = (agentId: string) => {
const stateRoot = const stateRoot =
process.env.OPENCLAW_STATE_DIR?.trim() || process.env.OPENCLAW_STATE_DIR?.trim() ||
process.env.CLAWDBOT_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() ||
path.join(os.homedir(), ".openclaw"); path.join(os.homedir(), ".openclaw");
const authPath = path.join(stateRoot, "agents", agentId, "agent", "auth-profiles.json"); const authPath = path.join(stateRoot, "agents", agentId, "agent", "auth-profiles.json");
if (!fs.existsSync(authPath)) throw new Error(`Missing: ${authPath}`); if (!fs.existsSync(authPath)) throw new Error(`Missing: ${authPath}`);
const store = JSON.parse(fs.readFileSync(authPath, "utf8")) as { const store = JSON.parse(fs.readFileSync(authPath, "utf8")) as {
@@ -99,8 +99,7 @@ const readClaudeCliKeychain = (): {
if (!oauth || typeof oauth !== "object") return null; if (!oauth || typeof oauth !== "object") return null;
const accessToken = oauth.accessToken; const accessToken = oauth.accessToken;
if (typeof accessToken !== "string" || !accessToken.trim()) return null; if (typeof accessToken !== "string" || !accessToken.trim()) return null;
const expiresAt = const expiresAt = typeof oauth.expiresAt === "number" ? oauth.expiresAt : undefined;
typeof oauth.expiresAt === "number" ? oauth.expiresAt : undefined;
const scopes = Array.isArray(oauth.scopes) const scopes = Array.isArray(oauth.scopes)
? oauth.scopes.filter((v): v is string => typeof v === "string") ? oauth.scopes.filter((v): v is string => typeof v === "string")
: undefined; : undefined;
@@ -120,11 +119,11 @@ const chromeServiceNameForPath = (cookiePath: string): string => {
const readKeychainPassword = (service: string): string | null => { const readKeychainPassword = (service: string): string | null => {
try { try {
const out = execFileSync( const out = execFileSync("security", ["find-generic-password", "-w", "-s", service], {
"security", encoding: "utf8",
["find-generic-password", "-w", "-s", service], stdio: ["ignore", "pipe", "ignore"],
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 }, timeout: 5000,
); });
const pw = out.trim(); const pw = out.trim();
return pw ? pw : null; return pw ? pw : null;
} catch { } catch {
@@ -317,15 +316,16 @@ const main = async () => {
process.env.CLAUDE_AI_SESSION_KEY?.trim() || process.env.CLAUDE_AI_SESSION_KEY?.trim() ||
process.env.CLAUDE_WEB_SESSION_KEY?.trim() || process.env.CLAUDE_WEB_SESSION_KEY?.trim() ||
findClaudeSessionKey()?.sessionKey; findClaudeSessionKey()?.sessionKey;
const source = const source = opts.sessionKey
opts.sessionKey ? "--session-key"
? "--session-key" : process.env.CLAUDE_AI_SESSION_KEY || process.env.CLAUDE_WEB_SESSION_KEY
: process.env.CLAUDE_AI_SESSION_KEY || process.env.CLAUDE_WEB_SESSION_KEY ? "env"
? "env" : (findClaudeSessionKey()?.source ?? "auto");
: findClaudeSessionKey()?.source ?? "auto";
if (!sessionKey) { if (!sessionKey) {
console.log("Claude web: no sessionKey found (try --session-key or export CLAUDE_AI_SESSION_KEY)"); console.log(
"Claude web: no sessionKey found (try --session-key or export CLAUDE_AI_SESSION_KEY)",
);
return; return;
} }

View File

@@ -1,26 +1,26 @@
#!/usr/bin/env node #!/usr/bin/env node
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from 'node:path'; import { join, relative } from "node:path";
process.stdout.on('error', (error) => { process.stdout.on("error", (error) => {
if (error?.code === 'EPIPE') { if (error?.code === "EPIPE") {
process.exit(0); process.exit(0);
} }
throw error; throw error;
}); });
const DOCS_DIR = join(process.cwd(), 'docs'); const DOCS_DIR = join(process.cwd(), "docs");
if (!existsSync(DOCS_DIR)) { if (!existsSync(DOCS_DIR)) {
console.error('docs:list: missing docs directory. Run from repo root.'); console.error("docs:list: missing docs directory. Run from repo root.");
process.exit(1); process.exit(1);
} }
if (!statSync(DOCS_DIR).isDirectory()) { if (!statSync(DOCS_DIR).isDirectory()) {
console.error('docs:list: docs path is not a directory.'); console.error("docs:list: docs path is not a directory.");
process.exit(1); process.exit(1);
} }
const EXCLUDED_DIRS = new Set(['archive', 'research']); const EXCLUDED_DIRS = new Set(["archive", "research"]);
/** /**
* @param {unknown[]} values * @param {unknown[]} values
@@ -49,7 +49,7 @@ function walkMarkdownFiles(dir, base = dir) {
const entries = readdirSync(dir, { withFileTypes: true }); const entries = readdirSync(dir, { withFileTypes: true });
const files = []; const files = [];
for (const entry of entries) { for (const entry of entries) {
if (entry.name.startsWith('.')) { if (entry.name.startsWith(".")) {
continue; continue;
} }
const fullPath = join(dir, entry.name); const fullPath = join(dir, entry.name);
@@ -58,7 +58,7 @@ function walkMarkdownFiles(dir, base = dir) {
continue; continue;
} }
files.push(...walkMarkdownFiles(fullPath, base)); files.push(...walkMarkdownFiles(fullPath, base));
} else if (entry.isFile() && entry.name.endsWith('.md')) { } else if (entry.isFile() && entry.name.endsWith(".md")) {
files.push(relative(base, fullPath)); files.push(relative(base, fullPath));
} }
} }
@@ -70,19 +70,19 @@ function walkMarkdownFiles(dir, base = dir) {
* @returns {{ summary: string | null; readWhen: string[]; error?: string }} * @returns {{ summary: string | null; readWhen: string[]; error?: string }}
*/ */
function extractMetadata(fullPath) { function extractMetadata(fullPath) {
const content = readFileSync(fullPath, 'utf8'); const content = readFileSync(fullPath, "utf8");
if (!content.startsWith('---')) { if (!content.startsWith("---")) {
return { summary: null, readWhen: [], error: 'missing front matter' }; return { summary: null, readWhen: [], error: "missing front matter" };
} }
const endIndex = content.indexOf('\n---', 3); const endIndex = content.indexOf("\n---", 3);
if (endIndex === -1) { if (endIndex === -1) {
return { summary: null, readWhen: [], error: 'unterminated front matter' }; return { summary: null, readWhen: [], error: "unterminated front matter" };
} }
const frontMatter = content.slice(3, endIndex).trim(); const frontMatter = content.slice(3, endIndex).trim();
const lines = frontMatter.split('\n'); const lines = frontMatter.split("\n");
let summaryLine = null; let summaryLine = null;
const readWhen = []; const readWhen = [];
@@ -91,16 +91,16 @@ function extractMetadata(fullPath) {
for (const rawLine of lines) { for (const rawLine of lines) {
const line = rawLine.trim(); const line = rawLine.trim();
if (line.startsWith('summary:')) { if (line.startsWith("summary:")) {
summaryLine = line; summaryLine = line;
collectingField = null; collectingField = null;
continue; continue;
} }
if (line.startsWith('read_when:')) { if (line.startsWith("read_when:")) {
collectingField = 'read_when'; collectingField = "read_when";
const inline = line.slice('read_when:'.length).trim(); const inline = line.slice("read_when:".length).trim();
if (inline.startsWith('[') && inline.endsWith(']')) { if (inline.startsWith("[") && inline.endsWith("]")) {
try { try {
const parsed = JSON.parse(inline.replace(/'/g, '"')); const parsed = JSON.parse(inline.replace(/'/g, '"'));
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
@@ -113,13 +113,13 @@ function extractMetadata(fullPath) {
continue; continue;
} }
if (collectingField === 'read_when') { if (collectingField === "read_when") {
if (line.startsWith('- ')) { if (line.startsWith("- ")) {
const hint = line.slice(2).trim(); const hint = line.slice(2).trim();
if (hint) { if (hint) {
readWhen.push(hint); readWhen.push(hint);
} }
} else if (line === '') { } else if (line === "") {
// allow blank lines inside the list // allow blank lines inside the list
} else { } else {
collectingField = null; collectingField = null;
@@ -128,23 +128,23 @@ function extractMetadata(fullPath) {
} }
if (!summaryLine) { if (!summaryLine) {
return { summary: null, readWhen, error: 'summary key missing' }; return { summary: null, readWhen, error: "summary key missing" };
} }
const summaryValue = summaryLine.slice('summary:'.length).trim(); const summaryValue = summaryLine.slice("summary:".length).trim();
const normalized = summaryValue const normalized = summaryValue
.replace(/^['"]|['"]$/g, '') .replace(/^['"]|['"]$/g, "")
.replace(/\s+/g, ' ') .replace(/\s+/g, " ")
.trim(); .trim();
if (!normalized) { if (!normalized) {
return { summary: null, readWhen, error: 'summary is empty' }; return { summary: null, readWhen, error: "summary is empty" };
} }
return { summary: normalized, readWhen }; return { summary: normalized, readWhen };
} }
console.log('Listing all markdown files in docs folder:'); console.log("Listing all markdown files in docs folder:");
const markdownFiles = walkMarkdownFiles(DOCS_DIR); const markdownFiles = walkMarkdownFiles(DOCS_DIR);
@@ -154,14 +154,14 @@ for (const relativePath of markdownFiles) {
if (summary) { if (summary) {
console.log(`${relativePath} - ${summary}`); console.log(`${relativePath} - ${summary}`);
if (readWhen.length > 0) { if (readWhen.length > 0) {
console.log(` Read when: ${readWhen.join('; ')}`); console.log(` Read when: ${readWhen.join("; ")}`);
} }
} else { } else {
const reason = error ? ` - [${error}]` : ''; const reason = error ? ` - [${error}]` : "";
console.log(`${relativePath}${reason}`); console.log(`${relativePath}${reason}`);
} }
} }
console.log( console.log(
'\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above (React hooks, cache directives, database work, tests, etc.), read that doc before coding, and suggest new coverage when it is missing.' '\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above (React hooks, cache directives, database work, tests, etc.), read that doc before coding, and suggest new coverage when it is missing.',
); );

View File

@@ -88,9 +88,7 @@ async function run() {
localError = error instanceof Error ? error.message : String(error); localError = error instanceof Error ? error.message : String(error);
} }
console.log( console.log(`local: ${localStatus} len=${localText.length} title=${truncate(localTitle, 80)}`);
`local: ${localStatus} len=${localText.length} title=${truncate(localTitle, 80)}`
);
if (localError) console.log(`local error: ${localError}`); if (localError) console.log(`local error: ${localError}`);
if (localText) console.log(`local sample: ${truncate(localText)}`); if (localText) console.log(`local sample: ${truncate(localText)}`);
@@ -111,7 +109,7 @@ async function run() {
`firecrawl: ok len=${firecrawl.text.length} title=${truncate( `firecrawl: ok len=${firecrawl.text.length} title=${truncate(
firecrawl.title ?? "", firecrawl.title ?? "",
80, 80,
)} status=${firecrawl.status ?? "n/a"}` )} status=${firecrawl.status ?? "n/a"}`,
); );
if (firecrawl.warning) console.log(`firecrawl warning: ${firecrawl.warning}`); if (firecrawl.warning) console.log(`firecrawl warning: ${firecrawl.warning}`);
if (firecrawl.text) console.log(`firecrawl sample: ${truncate(firecrawl.text)}`); if (firecrawl.text) console.log(`firecrawl sample: ${truncate(firecrawl.text)}`);

View File

@@ -3,16 +3,7 @@ import path from "node:path";
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
const OXFMT_EXTENSIONS = new Set([ const OXFMT_EXTENSIONS = new Set([".cjs", ".js", ".json", ".jsonc", ".jsx", ".mjs", ".ts", ".tsx"]);
".cjs",
".js",
".json",
".jsonc",
".jsx",
".mjs",
".ts",
".tsx",
]);
function getRepoRoot() { function getRepoRoot() {
const here = path.dirname(fileURLToPath(import.meta.url)); const here = path.dirname(fileURLToPath(import.meta.url));
@@ -40,9 +31,10 @@ function normalizeGitPath(filePath) {
function filterOxfmtTargets(paths) { function filterOxfmtTargets(paths) {
return paths return paths
.map(normalizeGitPath) .map(normalizeGitPath)
.filter((filePath) => .filter(
(filePath.startsWith("src/") || filePath.startsWith("test/")) && (filePath) =>
OXFMT_EXTENSIONS.has(path.posix.extname(filePath)), (filePath.startsWith("src/") || filePath.startsWith("test/")) &&
OXFMT_EXTENSIONS.has(path.posix.extname(filePath)),
); );
} }
@@ -94,13 +86,10 @@ function stageFiles(repoRoot, files) {
function main() { function main() {
const repoRoot = getRepoRoot(); const repoRoot = getRepoRoot();
const staged = getGitPaths([ const staged = getGitPaths(
"diff", ["diff", "--cached", "--name-only", "-z", "--diff-filter=ACMR"],
"--cached", repoRoot,
"--name-only", );
"-z",
"--diff-filter=ACMR",
], repoRoot);
const targets = filterOxfmtTargets(staged); const targets = filterOxfmtTargets(staged);
if (targets.length === 0) return; if (targets.length === 0) return;

View File

@@ -1,36 +1,34 @@
import fs from 'node:fs'; import fs from "node:fs";
import path from 'node:path'; import path from "node:path";
import { spawnSync } from 'node:child_process'; import { spawnSync } from "node:child_process";
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from "node:url";
import { setupGitHooks } from './setup-git-hooks.js'; import { setupGitHooks } from "./setup-git-hooks.js";
function detectPackageManager(ua = process.env.npm_config_user_agent ?? '') { function detectPackageManager(ua = process.env.npm_config_user_agent ?? "") {
// Examples: // Examples:
// - "pnpm/10.23.0 npm/? node/v22.21.1 darwin arm64" // - "pnpm/10.23.0 npm/? node/v22.21.1 darwin arm64"
// - "npm/10.9.4 node/v22.12.0 linux x64" // - "npm/10.9.4 node/v22.12.0 linux x64"
// - "bun/1.2.2" // - "bun/1.2.2"
const normalized = String(ua).trim(); const normalized = String(ua).trim();
if (normalized.startsWith('pnpm/')) return 'pnpm'; if (normalized.startsWith("pnpm/")) return "pnpm";
if (normalized.startsWith('bun/')) return 'bun'; if (normalized.startsWith("bun/")) return "bun";
if (normalized.startsWith('npm/')) return 'npm'; if (normalized.startsWith("npm/")) return "npm";
if (normalized.startsWith('yarn/')) return 'yarn'; if (normalized.startsWith("yarn/")) return "yarn";
return 'unknown'; return "unknown";
} }
function shouldApplyPnpmPatchedDependenciesFallback( function shouldApplyPnpmPatchedDependenciesFallback(pm = detectPackageManager()) {
pm = detectPackageManager(),
) {
// pnpm already applies pnpm.patchedDependencies itself; re-applying would fail. // pnpm already applies pnpm.patchedDependencies itself; re-applying would fail.
return pm !== 'pnpm'; return pm !== "pnpm";
} }
function getRepoRoot() { function getRepoRoot() {
const here = path.dirname(fileURLToPath(import.meta.url)); const here = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(here, '..'); return path.resolve(here, "..");
} }
function ensureExecutable(targetPath) { function ensureExecutable(targetPath) {
if (process.platform === 'win32') return; if (process.platform === "win32") return;
if (!fs.existsSync(targetPath)) return; if (!fs.existsSync(targetPath)) return;
try { try {
const mode = fs.statSync(targetPath).mode & 0o777; const mode = fs.statSync(targetPath).mode & 0o777;
@@ -42,32 +40,32 @@ function ensureExecutable(targetPath) {
} }
function hasGit(repoRoot) { function hasGit(repoRoot) {
const result = spawnSync('git', ['--version'], { const result = spawnSync("git", ["--version"], {
cwd: repoRoot, cwd: repoRoot,
stdio: 'ignore', stdio: "ignore",
}); });
return result.status === 0; return result.status === 0;
} }
function extractPackageName(key) { function extractPackageName(key) {
if (key.startsWith('@')) { if (key.startsWith("@")) {
const idx = key.indexOf('@', 1); const idx = key.indexOf("@", 1);
if (idx === -1) return key; if (idx === -1) return key;
return key.slice(0, idx); return key.slice(0, idx);
} }
const idx = key.lastIndexOf('@'); const idx = key.lastIndexOf("@");
if (idx <= 0) return key; if (idx <= 0) return key;
return key.slice(0, idx); return key.slice(0, idx);
} }
function stripPrefix(p) { function stripPrefix(p) {
if (p.startsWith('a/') || p.startsWith('b/')) return p.slice(2); if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
return p; return p;
} }
function parseRange(segment) { function parseRange(segment) {
// segment: "-12,5" or "+7" // segment: "-12,5" or "+7"
const [startRaw, countRaw] = segment.slice(1).split(','); const [startRaw, countRaw] = segment.slice(1).split(",");
const start = Number.parseInt(startRaw, 10); const start = Number.parseInt(startRaw, 10);
const count = countRaw ? Number.parseInt(countRaw, 10) : 1; const count = countRaw ? Number.parseInt(countRaw, 10) : 1;
if (Number.isNaN(start) || Number.isNaN(count)) { if (Number.isNaN(start) || Number.isNaN(count)) {
@@ -77,12 +75,12 @@ function parseRange(segment) {
} }
function parsePatch(patchText) { function parsePatch(patchText) {
const lines = patchText.split('\n'); const lines = patchText.split("\n");
const files = []; const files = [];
let i = 0; let i = 0;
while (i < lines.length) { while (i < lines.length) {
if (!lines[i].startsWith('diff --git ')) { if (!lines[i].startsWith("diff --git ")) {
i += 1; i += 1;
continue; continue;
} }
@@ -91,22 +89,20 @@ function parsePatch(patchText) {
i += 1; i += 1;
// Skip index line(s) // Skip index line(s)
while (i < lines.length && lines[i].startsWith('index ')) i += 1; while (i < lines.length && lines[i].startsWith("index ")) i += 1;
if (i < lines.length && lines[i].startsWith('--- ')) { if (i < lines.length && lines[i].startsWith("--- ")) {
file.oldPath = stripPrefix(lines[i].slice(4).trim()); file.oldPath = stripPrefix(lines[i].slice(4).trim());
i += 1; i += 1;
} }
if (i < lines.length && lines[i].startsWith('+++ ')) { if (i < lines.length && lines[i].startsWith("+++ ")) {
file.newPath = stripPrefix(lines[i].slice(4).trim()); file.newPath = stripPrefix(lines[i].slice(4).trim());
i += 1; i += 1;
} }
while (i < lines.length && lines[i].startsWith('@@')) { while (i < lines.length && lines[i].startsWith("@@")) {
const header = lines[i]; const header = lines[i];
const match = /^@@\s+(-\d+(?:,\d+)?)\s+(\+\d+(?:,\d+)?)\s+@@/.exec( const match = /^@@\s+(-\d+(?:,\d+)?)\s+(\+\d+(?:,\d+)?)\s+@@/.exec(header);
header,
);
if (!match) throw new Error(`invalid hunk header: ${header}`); if (!match) throw new Error(`invalid hunk header: ${header}`);
const oldRange = parseRange(match[1]); const oldRange = parseRange(match[1]);
const newRange = parseRange(match[2]); const newRange = parseRange(match[2]);
@@ -115,12 +111,12 @@ function parsePatch(patchText) {
const hunkLines = []; const hunkLines = [];
while (i < lines.length) { while (i < lines.length) {
const line = lines[i]; const line = lines[i];
if (line.startsWith('@@') || line.startsWith('diff --git ')) break; if (line.startsWith("@@") || line.startsWith("diff --git ")) break;
if (line === '') { if (line === "") {
i += 1; i += 1;
continue; continue;
} }
if (line.startsWith('\\ No newline at end of file')) { if (line.startsWith("\\ No newline at end of file")) {
i += 1; i += 1;
continue; continue;
} }
@@ -149,16 +145,16 @@ function readFileLines(targetPath) {
if (!fs.existsSync(targetPath)) { if (!fs.existsSync(targetPath)) {
throw new Error(`target file missing: ${targetPath}`); throw new Error(`target file missing: ${targetPath}`);
} }
const raw = fs.readFileSync(targetPath, 'utf-8'); const raw = fs.readFileSync(targetPath, "utf-8");
const hasTrailingNewline = raw.endsWith('\n'); const hasTrailingNewline = raw.endsWith("\n");
const parts = raw.split('\n'); const parts = raw.split("\n");
if (hasTrailingNewline) parts.pop(); if (hasTrailingNewline) parts.pop();
return { lines: parts, hasTrailingNewline }; return { lines: parts, hasTrailingNewline };
} }
function writeFileLines(targetPath, lines, hadTrailingNewline) { function writeFileLines(targetPath, lines, hadTrailingNewline) {
const content = lines.join('\n') + (hadTrailingNewline ? '\n' : ''); const content = lines.join("\n") + (hadTrailingNewline ? "\n" : "");
fs.writeFileSync(targetPath, content, 'utf-8'); fs.writeFileSync(targetPath, content, "utf-8");
} }
function applyHunk(lines, hunk, offset) { function applyHunk(lines, hunk, offset) {
@@ -166,7 +162,7 @@ function applyHunk(lines, hunk, offset) {
const expected = []; const expected = [];
for (const raw of hunk.lines) { for (const raw of hunk.lines) {
const marker = raw[0]; const marker = raw[0];
if (marker === ' ' || marker === '+') { if (marker === " " || marker === "+") {
expected.push(raw.slice(1)); expected.push(raw.slice(1));
} }
} }
@@ -187,21 +183,21 @@ function applyHunk(lines, hunk, offset) {
for (const raw of hunk.lines) { for (const raw of hunk.lines) {
const marker = raw[0]; const marker = raw[0];
const text = raw.slice(1); const text = raw.slice(1);
if (marker === ' ') { if (marker === " ") {
if (lines[cursor] !== text) { if (lines[cursor] !== text) {
throw new Error( throw new Error(
`context mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? '<eof>'}"`, `context mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? "<eof>"}"`,
); );
} }
cursor += 1; cursor += 1;
} else if (marker === '-') { } else if (marker === "-") {
if (lines[cursor] !== text) { if (lines[cursor] !== text) {
throw new Error( throw new Error(
`delete mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? '<eof>'}"`, `delete mismatch at line ${cursor + 1}: expected "${text}", found "${lines[cursor] ?? "<eof>"}"`,
); );
} }
lines.splice(cursor, 1); lines.splice(cursor, 1);
} else if (marker === '+') { } else if (marker === "+") {
lines.splice(cursor, 0, text); lines.splice(cursor, 0, text);
cursor += 1; cursor += 1;
} else { } else {
@@ -214,11 +210,11 @@ function applyHunk(lines, hunk, offset) {
} }
function applyPatchToFile(targetDir, filePatch) { function applyPatchToFile(targetDir, filePatch) {
if (filePatch.newPath === '/dev/null') { if (filePatch.newPath === "/dev/null") {
// deletion not needed for our patches // deletion not needed for our patches
return; return;
} }
const relPath = stripPrefix(filePatch.newPath ?? filePatch.oldPath ?? ''); const relPath = stripPrefix(filePatch.newPath ?? filePatch.oldPath ?? "");
const targetPath = path.join(targetDir, relPath); const targetPath = path.join(targetDir, relPath);
const { lines, hasTrailingNewline } = readFileLines(targetPath); const { lines, hasTrailingNewline } = readFileLines(targetPath);
@@ -232,10 +228,7 @@ function applyPatchToFile(targetDir, filePatch) {
function applyPatchSet({ patchText, targetDir }) { function applyPatchSet({ patchText, targetDir }) {
let resolvedTarget = path.resolve(targetDir); let resolvedTarget = path.resolve(targetDir);
if ( if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isDirectory()) {
!fs.existsSync(resolvedTarget) ||
!fs.statSync(resolvedTarget).isDirectory()
) {
console.warn(`[postinstall] skip missing target: ${resolvedTarget}`); console.warn(`[postinstall] skip missing target: ${resolvedTarget}`);
return; return;
} }
@@ -254,7 +247,7 @@ function applyPatchFile({ patchPath, targetDir }) {
if (!fs.existsSync(absPatchPath)) { if (!fs.existsSync(absPatchPath)) {
throw new Error(`missing patch: ${absPatchPath}`); throw new Error(`missing patch: ${absPatchPath}`);
} }
const patchText = fs.readFileSync(absPatchPath, 'utf-8'); const patchText = fs.readFileSync(absPatchPath, "utf-8");
applyPatchSet({ patchText, targetDir }); applyPatchSet({ patchText, targetDir });
} }
@@ -262,20 +255,20 @@ function trySetupCompletion(repoRoot) {
// Skip in CI or if explicitly disabled // Skip in CI or if explicitly disabled
if (process.env.CI || process.env.OPENCLAW_SKIP_COMPLETION_SETUP) return; if (process.env.CI || process.env.OPENCLAW_SKIP_COMPLETION_SETUP) return;
const binPath = path.join(repoRoot, 'openclaw.mjs'); const binPath = path.join(repoRoot, "openclaw.mjs");
if (!fs.existsSync(binPath)) return; if (!fs.existsSync(binPath)) return;
// In development, dist might not exist yet during postinstall // In development, dist might not exist yet during postinstall
const distEntry = path.join(repoRoot, 'dist', 'index.js'); const distEntry = path.join(repoRoot, "dist", "index.js");
if (!fs.existsSync(distEntry)) return; if (!fs.existsSync(distEntry)) return;
try { try {
// Run with OPENCLAW_SKIP_POSTINSTALL to avoid any weird recursion, // Run with OPENCLAW_SKIP_POSTINSTALL to avoid any weird recursion,
// though distinct from this script. // though distinct from this script.
spawnSync(process.execPath, [binPath, 'completion', '--install', '--yes'], { spawnSync(process.execPath, [binPath, "completion", "--install", "--yes"], {
cwd: repoRoot, cwd: repoRoot,
stdio: 'inherit', stdio: "inherit",
env: { ...process.env, OPENCLAW_SKIP_POSTINSTALL: '1' }, env: { ...process.env, OPENCLAW_SKIP_POSTINSTALL: "1" },
}); });
} catch (err) { } catch (err) {
// Ignore errors to not break install // Ignore errors to not break install
@@ -286,7 +279,7 @@ function main() {
const repoRoot = getRepoRoot(); const repoRoot = getRepoRoot();
process.chdir(repoRoot); process.chdir(repoRoot);
ensureExecutable(path.join(repoRoot, 'dist', '/entry.js')); ensureExecutable(path.join(repoRoot, "dist", "/entry.js"));
setupGitHooks({ repoRoot }); setupGitHooks({ repoRoot });
trySetupCompletion(repoRoot); trySetupCompletion(repoRoot);
@@ -294,18 +287,18 @@ function main() {
return; return;
} }
const pkgPath = path.join(repoRoot, 'package.json'); const pkgPath = path.join(repoRoot, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
const patched = pkg?.pnpm?.patchedDependencies ?? {}; const patched = pkg?.pnpm?.patchedDependencies ?? {};
// Bun does not support pnpm.patchedDependencies. Apply these patch files to // Bun does not support pnpm.patchedDependencies. Apply these patch files to
// node_modules packages as a best-effort compatibility layer. // node_modules packages as a best-effort compatibility layer.
for (const [key, relPatchPath] of Object.entries(patched)) { for (const [key, relPatchPath] of Object.entries(patched)) {
if (typeof relPatchPath !== 'string' || !relPatchPath.trim()) continue; if (typeof relPatchPath !== "string" || !relPatchPath.trim()) continue;
const pkgName = extractPackageName(String(key)); const pkgName = extractPackageName(String(key));
if (!pkgName) continue; if (!pkgName) continue;
applyPatchFile({ applyPatchFile({
targetDir: path.join('node_modules', ...pkgName.split('/')), targetDir: path.join("node_modules", ...pkgName.split("/")),
patchPath: relPatchPath, patchPath: relPatchPath,
}); });
} }
@@ -313,10 +306,10 @@ function main() {
try { try {
const skip = const skip =
process.env.OPENCLAW_SKIP_POSTINSTALL === '1' || process.env.OPENCLAW_SKIP_POSTINSTALL === "1" ||
process.env.CLAWDBOT_SKIP_POSTINSTALL === '1' || process.env.CLAWDBOT_SKIP_POSTINSTALL === "1" ||
process.env.VITEST === 'true' || process.env.VITEST === "true" ||
process.env.NODE_ENV === 'test'; process.env.NODE_ENV === "test";
if (!skip) { if (!skip) {
main(); main();

View File

@@ -1,11 +1,7 @@
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { import { ErrorCodes, PROTOCOL_VERSION, ProtocolSchemas } from "../src/gateway/protocol/schema.js";
ErrorCodes,
PROTOCOL_VERSION,
ProtocolSchemas,
} from "../src/gateway/protocol/schema.js";
type JsonSchema = { type JsonSchema = {
type?: string | string[]; type?: string | string[];
@@ -19,14 +15,7 @@ type JsonSchema = {
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, ".."); const repoRoot = path.resolve(__dirname, "..");
const outPaths = [ const outPaths = [
path.join( path.join(repoRoot, "apps", "macos", "Sources", "OpenClawProtocol", "GatewayModels.swift"),
repoRoot,
"apps",
"macos",
"Sources",
"OpenClawProtocol",
"GatewayModels.swift",
),
path.join( path.join(
repoRoot, repoRoot,
"apps", "apps",
@@ -38,7 +27,9 @@ const outPaths = [
), ),
]; ];
const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by hand\nimport Foundation\n\npublic let GATEWAY_PROTOCOL_VERSION = ${PROTOCOL_VERSION}\n\npublic enum ErrorCode: String, Codable, Sendable {\n${Object.values(ErrorCodes) const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by hand\nimport Foundation\n\npublic let GATEWAY_PROTOCOL_VERSION = ${PROTOCOL_VERSION}\n\npublic enum ErrorCode: String, Codable, Sendable {\n${Object.values(
ErrorCodes,
)
.map((c) => ` case ${camelCase(c)} = "${c}"`) .map((c) => ` case ${camelCase(c)} = "${c}"`)
.join("\n")}\n}\n`; .join("\n")}\n}\n`;
@@ -133,25 +124,27 @@ function emitStruct(name: string, schema: JsonSchema): string {
codingKeys.push(` case ${propName}`); codingKeys.push(` case ${propName}`);
} }
} }
lines.push("\n public init(\n" + lines.push(
Object.entries(props) "\n public init(\n" +
.map(([key, prop]) => { Object.entries(props)
const propName = safeName(key); .map(([key, prop]) => {
const req = required.has(key); const propName = safeName(key);
return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`; const req = required.has(key);
}) return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`;
.join(",\n") + })
"\n ) {\n" + .join(",\n") +
Object.entries(props) "\n ) {\n" +
.map(([key]) => { Object.entries(props)
const propName = safeName(key); .map(([key]) => {
return ` self.${propName} = ${propName}`; const propName = safeName(key);
}) return ` self.${propName} = ${propName}`;
.join("\n") + })
"\n }\n" + .join("\n") +
" private enum CodingKeys: String, CodingKey {\n" + "\n }\n" +
codingKeys.join("\n") + " private enum CodingKeys: String, CodingKey {\n" +
"\n }\n}"); codingKeys.join("\n") +
"\n }\n}",
);
lines.push(""); lines.push("");
return lines.join("\n"); return lines.join("\n");
} }
@@ -209,9 +202,7 @@ function emitGatewayFrame(): string {
} }
async function generate() { async function generate() {
const definitions = Object.entries(ProtocolSchemas) as Array< const definitions = Object.entries(ProtocolSchemas) as Array<[string, JsonSchema]>;
[string, JsonSchema]
>;
for (const [name, schema] of definitions) { for (const [name, schema] of definitions) {
schemaNameByObject.set(schema as object, name); schemaNameByObject.set(schema as object, name);

View File

@@ -7,11 +7,7 @@ import { join, resolve } from "node:path";
type PackFile = { path: string }; type PackFile = { path: string };
type PackResult = { files?: PackFile[] }; type PackResult = { files?: PackFile[] };
const requiredPaths = [ const requiredPaths = ["dist/discord/send.js", "dist/hooks/gmail.js", "dist/whatsapp/normalize.js"];
"dist/discord/send.js",
"dist/hooks/gmail.js",
"dist/whatsapp/normalize.js",
];
const forbiddenPrefixes = ["dist/OpenClaw.app/"]; const forbiddenPrefixes = ["dist/OpenClaw.app/"];
type PackageJson = { type PackageJson = {

View File

@@ -1,24 +1,21 @@
#!/usr/bin/env node #!/usr/bin/env node
import { spawn } from 'node:child_process'; import { spawn } from "node:child_process";
import fs from 'node:fs'; import fs from "node:fs";
import path from 'node:path'; import path from "node:path";
import process from 'node:process'; import process from "node:process";
const args = process.argv.slice(2); const args = process.argv.slice(2);
const env = { ...process.env }; const env = { ...process.env };
const cwd = process.cwd(); const cwd = process.cwd();
const compilerOverride = env.OPENCLAW_TS_COMPILER ?? env.CLAWDBOT_TS_COMPILER; const compilerOverride = env.OPENCLAW_TS_COMPILER ?? env.CLAWDBOT_TS_COMPILER;
const compiler = compilerOverride === 'tsc' ? 'tsc' : 'tsgo'; const compiler = compilerOverride === "tsc" ? "tsc" : "tsgo";
const projectArgs = ['--project', 'tsconfig.json']; const projectArgs = ["--project", "tsconfig.json"];
const distRoot = path.join(cwd, 'dist'); const distRoot = path.join(cwd, "dist");
const distEntry = path.join(distRoot, '/entry.js'); const distEntry = path.join(distRoot, "/entry.js");
const buildStampPath = path.join(distRoot, '.buildstamp'); const buildStampPath = path.join(distRoot, ".buildstamp");
const srcRoot = path.join(cwd, 'src'); const srcRoot = path.join(cwd, "src");
const configFiles = [ const configFiles = [path.join(cwd, "tsconfig.json"), path.join(cwd, "package.json")];
path.join(cwd, 'tsconfig.json'),
path.join(cwd, 'package.json'),
];
const statMtime = (filePath) => { const statMtime = (filePath) => {
try { try {
@@ -30,10 +27,10 @@ const statMtime = (filePath) => {
const isExcludedSource = (filePath) => { const isExcludedSource = (filePath) => {
const relativePath = path.relative(srcRoot, filePath); const relativePath = path.relative(srcRoot, filePath);
if (relativePath.startsWith('..')) return false; if (relativePath.startsWith("..")) return false;
return ( return (
relativePath.endsWith('.test.ts') || relativePath.endsWith(".test.ts") ||
relativePath.endsWith('.test.tsx') || relativePath.endsWith(".test.tsx") ||
relativePath.endsWith(`test-helpers.ts`) relativePath.endsWith(`test-helpers.ts`)
); );
}; };
@@ -69,7 +66,7 @@ const findLatestMtime = (dirPath, shouldSkip) => {
}; };
const shouldBuild = () => { const shouldBuild = () => {
if (env.OPENCLAW_FORCE_BUILD === '1') return true; if (env.OPENCLAW_FORCE_BUILD === "1") return true;
const stampMtime = statMtime(buildStampPath); const stampMtime = statMtime(buildStampPath);
if (stampMtime == null) return true; if (stampMtime == null) return true;
if (statMtime(distEntry) == null) return true; if (statMtime(distEntry) == null) return true;
@@ -85,18 +82,18 @@ const shouldBuild = () => {
}; };
const logRunner = (message) => { const logRunner = (message) => {
if (env.OPENCLAW_RUNNER_LOG === '0') return; if (env.OPENCLAW_RUNNER_LOG === "0") return;
process.stderr.write(`[openclaw] ${message}\n`); process.stderr.write(`[openclaw] ${message}\n`);
}; };
const runNode = () => { const runNode = () => {
const nodeProcess = spawn(process.execPath, ['openclaw.mjs', ...args], { const nodeProcess = spawn(process.execPath, ["openclaw.mjs", ...args], {
cwd, cwd,
env, env,
stdio: 'inherit', stdio: "inherit",
}); });
nodeProcess.on('exit', (exitCode, exitSignal) => { nodeProcess.on("exit", (exitCode, exitSignal) => {
if (exitSignal) { if (exitSignal) {
process.exit(1); process.exit(1);
} }
@@ -110,29 +107,25 @@ const writeBuildStamp = () => {
fs.writeFileSync(buildStampPath, `${Date.now()}\n`); fs.writeFileSync(buildStampPath, `${Date.now()}\n`);
} catch (error) { } catch (error) {
// Best-effort stamp; still allow the runner to start. // Best-effort stamp; still allow the runner to start.
logRunner( logRunner(`Failed to write build stamp: ${error?.message ?? "unknown error"}`);
`Failed to write build stamp: ${error?.message ?? 'unknown error'}`,
);
} }
}; };
if (!shouldBuild()) { if (!shouldBuild()) {
runNode(); runNode();
} else { } else {
logRunner('Building TypeScript (dist is stale).'); logRunner("Building TypeScript (dist is stale).");
const pnpmArgs = ['exec', compiler, ...projectArgs]; const pnpmArgs = ["exec", compiler, ...projectArgs];
const buildCmd = process.platform === 'win32' ? 'cmd.exe' : 'pnpm'; const buildCmd = process.platform === "win32" ? "cmd.exe" : "pnpm";
const buildArgs = const buildArgs =
process.platform === 'win32' process.platform === "win32" ? ["/d", "/s", "/c", "pnpm", ...pnpmArgs] : pnpmArgs;
? ['/d', '/s', '/c', 'pnpm', ...pnpmArgs]
: pnpmArgs;
const build = spawn(buildCmd, buildArgs, { const build = spawn(buildCmd, buildArgs, {
cwd, cwd,
env, env,
stdio: 'inherit', stdio: "inherit",
}); });
build.on('exit', (code, signal) => { build.on("exit", (code, signal) => {
if (signal) { if (signal) {
process.exit(1); process.exit(1);
} }

View File

@@ -31,9 +31,7 @@ insert.run("c", vec([0.2, 0.2, 0, 0]));
const query = vec([1, 0, 0, 0]); const query = vec([1, 0, 0, 0]);
const rows = db const rows = db
.prepare( .prepare("SELECT id, vec_distance_cosine(embedding, ?) AS dist FROM v ORDER BY dist ASC")
"SELECT id, vec_distance_cosine(embedding, ?) AS dist FROM v ORDER BY dist ASC"
)
.all(query); .all(query);
console.log("sqlite-vec ok"); console.log("sqlite-vec ok");

View File

@@ -36,16 +36,7 @@ for (const label of missing) {
const color = pickColor(label); const color = pickColor(label);
execFileSync( execFileSync(
"gh", "gh",
[ ["api", "-X", "POST", `repos/${repo}/labels`, "-f", `name=${label}`, "-f", `color=${color}`],
"api",
"-X",
"POST",
`repos/${repo}/labels`,
"-f",
`name=${label}`,
"-f",
`color=${color}`,
],
{ stdio: "inherit" }, { stdio: "inherit" },
); );
console.log(`Created label: ${label}`); console.log(`Created label: ${label}`);
@@ -97,11 +88,9 @@ function resolveRepo(): string {
} }
function fetchExistingLabels(repo: string): Map<string, RepoLabel> { function fetchExistingLabels(repo: string): Map<string, RepoLabel> {
const raw = execFileSync( const raw = execFileSync("gh", ["api", `repos/${repo}/labels?per_page=100`, "--paginate"], {
"gh", encoding: "utf8",
["api", `repos/${repo}/labels?per_page=100`, "--paginate"], });
{ encoding: "utf8" },
);
const labels = JSON.parse(raw) as RepoLabel[]; const labels = JSON.parse(raw) as RepoLabel[];
return new Map(labels.map((label) => [label.name, label])); return new Map(labels.map((label) => [label.name, label]));
} }

View File

@@ -49,9 +49,7 @@ function replaceBlockLines(
} }
function renderKimiK2Ids(prefix: string) { function renderKimiK2Ids(prefix: string) {
return MOONSHOT_KIMI_K2_MODELS.map( return MOONSHOT_KIMI_K2_MODELS.map((model) => `- \`${prefix}${model.id}\``);
(model) => `- \`${prefix}${model.id}\``,
);
} }
function renderMoonshotAliases() { function renderMoonshotAliases() {
@@ -85,10 +83,7 @@ function renderMoonshotModels() {
async function syncMoonshotDocs() { async function syncMoonshotDocs() {
const moonshotDoc = path.join(repoRoot, "docs/providers/moonshot.md"); const moonshotDoc = path.join(repoRoot, "docs/providers/moonshot.md");
const conceptsDoc = path.join( const conceptsDoc = path.join(repoRoot, "docs/concepts/model-providers.md");
repoRoot,
"docs/concepts/model-providers.md",
);
let moonshotText = await readFile(moonshotDoc, "utf8"); let moonshotText = await readFile(moonshotDoc, "utf8");
moonshotText = replaceBlockLines( moonshotText = replaceBlockLines(

View File

@@ -16,7 +16,9 @@ if (!targetVersion) {
} }
const extensionsDir = resolve("extensions"); const extensionsDir = resolve("extensions");
const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()); const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
entry.isDirectory(),
);
const updated: string[] = []; const updated: string[] = [];
const changelogged: string[] = []; const changelogged: string[] = [];
@@ -67,5 +69,5 @@ for (const dir of dirs) {
} }
console.log( console.log(
`Synced plugin versions to ${targetVersion}. Updated: ${updated.length}. Changelogged: ${changelogged.length}. Skipped: ${skipped.length}.` `Synced plugin versions to ${targetVersion}. Updated: ${updated.length}. Changelogged: ${changelogged.length}. Skipped: ${skipped.length}.`,
); );

View File

@@ -24,10 +24,17 @@ const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macO
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows"; const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
const isWindowsCi = isCI && isWindows; const isWindowsCi = isCI && isWindows;
const shardOverride = Number.parseInt(process.env.OPENCLAW_TEST_SHARDS ?? "", 10); const shardOverride = Number.parseInt(process.env.OPENCLAW_TEST_SHARDS ?? "", 10);
const shardCount = isWindowsCi ? (Number.isFinite(shardOverride) && shardOverride > 1 ? shardOverride : 2) : 1; const shardCount = isWindowsCi
const windowsCiArgs = isWindowsCi ? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"] : []; ? Number.isFinite(shardOverride) && shardOverride > 1
? shardOverride
: 2
: 1;
const windowsCiArgs = isWindowsCi
? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"]
: [];
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10); const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null; const resolvedOverride =
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway"); const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway");
const serialRuns = isWindowsCi ? runs : runs.filter((entry) => entry.name === "gateway"); const serialRuns = isWindowsCi ? runs : runs.filter((entry) => entry.name === "gateway");
const localWorkers = Math.max(4, Math.min(16, os.cpus().length)); const localWorkers = Math.max(4, Math.min(16, os.cpus().length));

View File

@@ -11,9 +11,7 @@ const uiDir = path.join(repoRoot, "ui");
function usage() { function usage() {
// keep this tiny; it's invoked from npm scripts too // keep this tiny; it's invoked from npm scripts too
process.stderr.write( process.stderr.write("Usage: node scripts/ui.js <install|dev|build|test> [...args]\n");
"Usage: node scripts/ui.js <install|dev|build|test> [...args]\n",
);
} }
function which(cmd) { function which(cmd) {
@@ -24,9 +22,7 @@ function which(cmd) {
.filter(Boolean); .filter(Boolean);
const extensions = const extensions =
process.platform === "win32" process.platform === "win32"
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM") ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)
.split(";")
.filter(Boolean)
: [""]; : [""];
for (const entry of paths) { for (const entry of paths) {
for (const ext of extensions) { for (const ext of extensions) {
@@ -122,11 +118,8 @@ if (action === "install") run(runner.cmd, ["install", ...rest]);
else { else {
if (!depsInstalled(action === "test" ? "test" : "build")) { if (!depsInstalled(action === "test" ? "test" : "build")) {
const installEnv = const installEnv =
action === "build" action === "build" ? { ...process.env, NODE_ENV: "production" } : process.env;
? { ...process.env, NODE_ENV: "production" } const installArgs = action === "build" ? ["install", "--prod"] : ["install"];
: process.env;
const installArgs =
action === "build" ? ["install", "--prod"] : ["install"];
runSync(runner.cmd, installArgs, installEnv); runSync(runner.cmd, installArgs, installEnv);
} }
run(runner.cmd, ["run", script, ...rest]); run(runner.cmd, ["run", script, ...rest]);

View File

@@ -329,7 +329,7 @@ function resolveLogin(
email: string | null, email: string | null,
apiByLogin: Map<string, User>, apiByLogin: Map<string, User>,
nameToLogin: Record<string, string>, nameToLogin: Record<string, string>,
emailToLogin: Record<string, string> emailToLogin: Record<string, string>,
): string | null { ): string | null {
if (email && emailToLogin[email]) { if (email && emailToLogin[email]) {
return emailToLogin[email]; return emailToLogin[email];
@@ -379,7 +379,7 @@ function resolveLogin(
function guessLoginFromEmailName( function guessLoginFromEmailName(
name: string, name: string,
email: string, email: string,
apiByLogin: Map<string, User> apiByLogin: Map<string, User>,
): string | null { ): string | null {
const local = email.split("@", 1)[0]?.trim(); const local = email.split("@", 1)[0]?.trim();
if (!local) { if (!local) {
@@ -410,7 +410,7 @@ function normalizeIdentifier(value: string): string {
} }
function parseReadmeEntries( function parseReadmeEntries(
content: string content: string,
): Array<{ display: string; html_url: string; avatar_url: string }> { ): Array<{ display: string; html_url: string; avatar_url: string }> {
const start = content.indexOf('<p align="left">'); const start = content.indexOf('<p align="left">');
const end = content.indexOf("</p>", start); const end = content.indexOf("</p>", start);
@@ -458,7 +458,11 @@ function fallbackHref(value: string): string {
return encoded ? `https://github.com/search?q=${encoded}` : "https://github.com"; return encoded ? `https://github.com/search?q=${encoded}` : "https://github.com";
} }
function pickDisplay(baseName: string | null | undefined, login: string, existing?: string): string { function pickDisplay(
baseName: string | null | undefined,
login: string,
existing?: string,
): string {
const key = login.toLowerCase(); const key = login.toLowerCase();
if (displayName[key]) { if (displayName[key]) {
return displayName[key]; return displayName[key];

View File

@@ -42,7 +42,4 @@ const buildInfo = {
}; };
fs.mkdirSync(distDir, { recursive: true }); fs.mkdirSync(distDir, { recursive: true });
fs.writeFileSync( fs.writeFileSync(path.join(distDir, "build-info.json"), `${JSON.stringify(buildInfo, null, 2)}\n`);
path.join(distDir, "build-info.json"),
`${JSON.stringify(buildInfo, null, 2)}\n`,
);

View File

@@ -20,9 +20,7 @@ function pickAnthropicEnv(): { type: "oauth" | "api"; value: string } | null {
} }
function pickZaiKey(): string | null { function pickZaiKey(): string | null {
return ( return process.env.ZAI_API_KEY?.trim() ?? process.env.Z_AI_API_KEY?.trim() ?? null;
process.env.ZAI_API_KEY?.trim() ?? process.env.Z_AI_API_KEY?.trim() ?? null
);
} }
async function runCommand( async function runCommand(
@@ -74,9 +72,7 @@ async function main() {
process.exit(1); process.exit(1);
} }
const baseDir = await fs.mkdtemp( const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-zai-fallback-"));
path.join(os.tmpdir(), "openclaw-zai-fallback-"),
);
const stateDir = path.join(baseDir, "state"); const stateDir = path.join(baseDir, "state");
const configPath = path.join(baseDir, "openclaw.json"); const configPath = path.join(baseDir, "openclaw.json");
await fs.mkdir(stateDir, { recursive: true }); await fs.mkdir(stateDir, { recursive: true });
@@ -130,28 +126,14 @@ async function main() {
"Then use the read tool to display the file contents. Reply with just the file contents."; "Then use the read tool to display the file contents. Reply with just the file contents.";
const run1 = await runCommand( const run1 = await runCommand(
"run1", "run1",
[ ["openclaw", "agent", "--local", "--session-id", sessionId, "--message", toolPrompt],
"openclaw",
"agent",
"--local",
"--session-id",
sessionId,
"--message",
toolPrompt,
],
envValidAnthropic, envValidAnthropic,
); );
if (run1.code !== 0) { if (run1.code !== 0) {
process.exit(run1.code ?? 1); process.exit(run1.code ?? 1);
} }
const sessionFile = path.join( const sessionFile = path.join(stateDir, "agents", "main", "sessions", `${sessionId}.jsonl`);
stateDir,
"agents",
"main",
"sessions",
`${sessionId}.jsonl`,
);
const transcript = await fs.readFile(sessionFile, "utf8").catch(() => ""); const transcript = await fs.readFile(sessionFile, "utf8").catch(() => "");
if (!transcript.includes('"toolResult"')) { if (!transcript.includes('"toolResult"')) {
console.warn("Warning: no toolResult entries detected in session history."); console.warn("Warning: no toolResult entries detected in session history.");
@@ -162,15 +144,7 @@ async function main() {
"What is the content of zai-fallback-tool.txt? Reply with just the contents."; "What is the content of zai-fallback-tool.txt? Reply with just the contents.";
const run2 = await runCommand( const run2 = await runCommand(
"run2", "run2",
[ ["openclaw", "agent", "--local", "--session-id", sessionId, "--message", followupPrompt],
"openclaw",
"agent",
"--local",
"--session-id",
sessionId,
"--message",
followupPrompt,
],
envInvalidAnthropic, envInvalidAnthropic,
); );

View File

@@ -2,7 +2,24 @@
name: 1password name: 1password
description: Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op. description: Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
homepage: https://developer.1password.com/docs/cli/get-started/ homepage: https://developer.1password.com/docs/cli/get-started/
metadata: {"openclaw":{"emoji":"🔐","requires":{"bins":["op"]},"install":[{"id":"brew","kind":"brew","formula":"1password-cli","bins":["op"],"label":"Install 1Password CLI (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🔐",
"requires": { "bins": ["op"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "1password-cli",
"bins": ["op"],
"label": "Install 1Password CLI (brew)",
},
],
},
}
--- ---
# 1Password CLI # 1Password CLI

View File

@@ -2,7 +2,25 @@
name: apple-notes name: apple-notes
description: Manage Apple Notes via the `memo` CLI on macOS (create, view, edit, delete, search, move, and export notes). Use when a user asks OpenClaw to add a note, list notes, search notes, or manage note folders. description: Manage Apple Notes via the `memo` CLI on macOS (create, view, edit, delete, search, move, and export notes). Use when a user asks OpenClaw to add a note, list notes, search notes, or manage note folders.
homepage: https://github.com/antoniorodr/memo homepage: https://github.com/antoniorodr/memo
metadata: {"openclaw":{"emoji":"📝","os":["darwin"],"requires":{"bins":["memo"]},"install":[{"id":"brew","kind":"brew","formula":"antoniorodr/memo/memo","bins":["memo"],"label":"Install memo via Homebrew"}]}} metadata:
{
"openclaw":
{
"emoji": "📝",
"os": ["darwin"],
"requires": { "bins": ["memo"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "antoniorodr/memo/memo",
"bins": ["memo"],
"label": "Install memo via Homebrew",
},
],
},
}
--- ---
# Apple Notes CLI # Apple Notes CLI
@@ -10,41 +28,50 @@ metadata: {"openclaw":{"emoji":"📝","os":["darwin"],"requires":{"bins":["memo"
Use `memo notes` to manage Apple Notes directly from the terminal. Create, view, edit, delete, search, move notes between folders, and export to HTML/Markdown. Use `memo notes` to manage Apple Notes directly from the terminal. Create, view, edit, delete, search, move notes between folders, and export to HTML/Markdown.
Setup Setup
- Install (Homebrew): `brew tap antoniorodr/memo && brew install antoniorodr/memo/memo` - Install (Homebrew): `brew tap antoniorodr/memo && brew install antoniorodr/memo/memo`
- Manual (pip): `pip install .` (after cloning the repo) - Manual (pip): `pip install .` (after cloning the repo)
- macOS-only; if prompted, grant Automation access to Notes.app. - macOS-only; if prompted, grant Automation access to Notes.app.
View Notes View Notes
- List all notes: `memo notes` - List all notes: `memo notes`
- Filter by folder: `memo notes -f "Folder Name"` - Filter by folder: `memo notes -f "Folder Name"`
- Search notes (fuzzy): `memo notes -s "query"` - Search notes (fuzzy): `memo notes -s "query"`
Create Notes Create Notes
- Add a new note: `memo notes -a` - Add a new note: `memo notes -a`
- Opens an interactive editor to compose the note. - Opens an interactive editor to compose the note.
- Quick add with title: `memo notes -a "Note Title"` - Quick add with title: `memo notes -a "Note Title"`
Edit Notes Edit Notes
- Edit existing note: `memo notes -e` - Edit existing note: `memo notes -e`
- Interactive selection of note to edit. - Interactive selection of note to edit.
Delete Notes Delete Notes
- Delete a note: `memo notes -d` - Delete a note: `memo notes -d`
- Interactive selection of note to delete. - Interactive selection of note to delete.
Move Notes Move Notes
- Move note to folder: `memo notes -m` - Move note to folder: `memo notes -m`
- Interactive selection of note and destination folder. - Interactive selection of note and destination folder.
Export Notes Export Notes
- Export to HTML/Markdown: `memo notes -ex` - Export to HTML/Markdown: `memo notes -ex`
- Exports selected note; uses Mistune for markdown processing. - Exports selected note; uses Mistune for markdown processing.
Limitations Limitations
- Cannot edit notes containing images or attachments. - Cannot edit notes containing images or attachments.
- Interactive prompts may require terminal access. - Interactive prompts may require terminal access.
Notes Notes
- macOS-only. - macOS-only.
- Requires Apple Notes.app to be accessible. - Requires Apple Notes.app to be accessible.
- For automation, grant permissions in System Settings > Privacy & Security > Automation. - For automation, grant permissions in System Settings > Privacy & Security > Automation.

View File

@@ -2,7 +2,25 @@
name: apple-reminders name: apple-reminders
description: Manage Apple Reminders via the `remindctl` CLI on macOS (list, add, edit, complete, delete). Supports lists, date filters, and JSON/plain output. description: Manage Apple Reminders via the `remindctl` CLI on macOS (list, add, edit, complete, delete). Supports lists, date filters, and JSON/plain output.
homepage: https://github.com/steipete/remindctl homepage: https://github.com/steipete/remindctl
metadata: {"openclaw":{"emoji":"⏰","os":["darwin"],"requires":{"bins":["remindctl"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/remindctl","bins":["remindctl"],"label":"Install remindctl via Homebrew"}]}} metadata:
{
"openclaw":
{
"emoji": "⏰",
"os": ["darwin"],
"requires": { "bins": ["remindctl"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/remindctl",
"bins": ["remindctl"],
"label": "Install remindctl via Homebrew",
},
],
},
}
--- ---
# Apple Reminders CLI (remindctl) # Apple Reminders CLI (remindctl)
@@ -10,15 +28,18 @@ metadata: {"openclaw":{"emoji":"⏰","os":["darwin"],"requires":{"bins":["remind
Use `remindctl` to manage Apple Reminders directly from the terminal. It supports list filtering, date-based views, and scripting output. Use `remindctl` to manage Apple Reminders directly from the terminal. It supports list filtering, date-based views, and scripting output.
Setup Setup
- Install (Homebrew): `brew install steipete/tap/remindctl` - Install (Homebrew): `brew install steipete/tap/remindctl`
- From source: `pnpm install && pnpm build` (binary at `./bin/remindctl`) - From source: `pnpm install && pnpm build` (binary at `./bin/remindctl`)
- macOS-only; grant Reminders permission when prompted. - macOS-only; grant Reminders permission when prompted.
Permissions Permissions
- Check status: `remindctl status` - Check status: `remindctl status`
- Request access: `remindctl authorize` - Request access: `remindctl authorize`
View Reminders View Reminders
- Default (today): `remindctl` - Default (today): `remindctl`
- Today: `remindctl today` - Today: `remindctl today`
- Tomorrow: `remindctl tomorrow` - Tomorrow: `remindctl tomorrow`
@@ -30,6 +51,7 @@ View Reminders
- Specific date: `remindctl 2026-01-04` - Specific date: `remindctl 2026-01-04`
Manage Lists Manage Lists
- List all lists: `remindctl list` - List all lists: `remindctl list`
- Show list: `remindctl list Work` - Show list: `remindctl list Work`
- Create list: `remindctl list Projects --create` - Create list: `remindctl list Projects --create`
@@ -37,31 +59,38 @@ Manage Lists
- Delete list: `remindctl list Work --delete` - Delete list: `remindctl list Work --delete`
Create Reminders Create Reminders
- Quick add: `remindctl add "Buy milk"` - Quick add: `remindctl add "Buy milk"`
- With list + due: `remindctl add --title "Call mom" --list Personal --due tomorrow` - With list + due: `remindctl add --title "Call mom" --list Personal --due tomorrow`
Edit Reminders Edit Reminders
- Edit title/due: `remindctl edit 1 --title "New title" --due 2026-01-04` - Edit title/due: `remindctl edit 1 --title "New title" --due 2026-01-04`
Complete Reminders Complete Reminders
- Complete by id: `remindctl complete 1 2 3` - Complete by id: `remindctl complete 1 2 3`
Delete Reminders Delete Reminders
- Delete by id: `remindctl delete 4A83 --force` - Delete by id: `remindctl delete 4A83 --force`
Output Formats Output Formats
- JSON (scripting): `remindctl today --json` - JSON (scripting): `remindctl today --json`
- Plain TSV: `remindctl today --plain` - Plain TSV: `remindctl today --plain`
- Counts only: `remindctl today --quiet` - Counts only: `remindctl today --quiet`
Date Formats Date Formats
Accepted by `--due` and date filters: Accepted by `--due` and date filters:
- `today`, `tomorrow`, `yesterday` - `today`, `tomorrow`, `yesterday`
- `YYYY-MM-DD` - `YYYY-MM-DD`
- `YYYY-MM-DD HH:mm` - `YYYY-MM-DD HH:mm`
- ISO 8601 (`2026-01-04T12:34:56Z`) - ISO 8601 (`2026-01-04T12:34:56Z`)
Notes Notes
- macOS-only. - macOS-only.
- If access is denied, enable Terminal/remindctl in System Settings → Privacy & Security → Reminders. - If access is denied, enable Terminal/remindctl in System Settings → Privacy & Security → Reminders.
- If running over SSH, grant access on the Mac that runs the command. - If running over SSH, grant access on the Mac that runs the command.

View File

@@ -2,7 +2,25 @@
name: bear-notes name: bear-notes
description: Create, search, and manage Bear notes via grizzly CLI. description: Create, search, and manage Bear notes via grizzly CLI.
homepage: https://bear.app homepage: https://bear.app
metadata: {"openclaw":{"emoji":"🐻","os":["darwin"],"requires":{"bins":["grizzly"]},"install":[{"id":"go","kind":"go","module":"github.com/tylerwince/grizzly/cmd/grizzly@latest","bins":["grizzly"],"label":"Install grizzly (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "🐻",
"os": ["darwin"],
"requires": { "bins": ["grizzly"] },
"install":
[
{
"id": "go",
"kind": "go",
"module": "github.com/tylerwince/grizzly/cmd/grizzly@latest",
"bins": ["grizzly"],
"label": "Install grizzly (go)",
},
],
},
}
--- ---
# Bear Notes # Bear Notes
@@ -10,39 +28,46 @@ metadata: {"openclaw":{"emoji":"🐻","os":["darwin"],"requires":{"bins":["grizz
Use `grizzly` to create, read, and manage notes in Bear on macOS. Use `grizzly` to create, read, and manage notes in Bear on macOS.
Requirements Requirements
- Bear app installed and running - Bear app installed and running
- For some operations (add-text, tags, open-note --selected), a Bear app token (stored in `~/.config/grizzly/token`) - For some operations (add-text, tags, open-note --selected), a Bear app token (stored in `~/.config/grizzly/token`)
## Getting a Bear Token ## Getting a Bear Token
For operations that require a token (add-text, tags, open-note --selected), you need an authentication token: For operations that require a token (add-text, tags, open-note --selected), you need an authentication token:
1. Open Bear → Help → API Token → Copy Token 1. Open Bear → Help → API Token → Copy Token
2. Save it: `echo "YOUR_TOKEN" > ~/.config/grizzly/token` 2. Save it: `echo "YOUR_TOKEN" > ~/.config/grizzly/token`
## Common Commands ## Common Commands
Create a note Create a note
```bash ```bash
echo "Note content here" | grizzly create --title "My Note" --tag work echo "Note content here" | grizzly create --title "My Note" --tag work
grizzly create --title "Quick Note" --tag inbox < /dev/null grizzly create --title "Quick Note" --tag inbox < /dev/null
``` ```
Open/read a note by ID Open/read a note by ID
```bash ```bash
grizzly open-note --id "NOTE_ID" --enable-callback --json grizzly open-note --id "NOTE_ID" --enable-callback --json
``` ```
Append text to a note Append text to a note
```bash ```bash
echo "Additional content" | grizzly add-text --id "NOTE_ID" --mode append --token-file ~/.config/grizzly/token echo "Additional content" | grizzly add-text --id "NOTE_ID" --mode append --token-file ~/.config/grizzly/token
``` ```
List all tags List all tags
```bash ```bash
grizzly tags --enable-callback --json --token-file ~/.config/grizzly/token grizzly tags --enable-callback --json --token-file ~/.config/grizzly/token
``` ```
Search notes (via open-tag) Search notes (via open-tag)
```bash ```bash
grizzly open-tag --name "work" --enable-callback --json grizzly open-tag --name "work" --enable-callback --json
``` ```
@@ -50,6 +75,7 @@ grizzly open-tag --name "work" --enable-callback --json
## Options ## Options
Common flags: Common flags:
- `--dry-run` — Preview the URL without executing - `--dry-run` — Preview the URL without executing
- `--print-url` — Show the x-callback-url - `--print-url` — Show the x-callback-url
- `--enable-callback` — Wait for Bear's response (needed for reading data) - `--enable-callback` — Wait for Bear's response (needed for reading data)
@@ -59,12 +85,14 @@ Common flags:
## Configuration ## Configuration
Grizzly reads config from (in priority order): Grizzly reads config from (in priority order):
1. CLI flags 1. CLI flags
2. Environment variables (`GRIZZLY_TOKEN_FILE`, `GRIZZLY_CALLBACK_URL`, `GRIZZLY_TIMEOUT`) 2. Environment variables (`GRIZZLY_TOKEN_FILE`, `GRIZZLY_CALLBACK_URL`, `GRIZZLY_TIMEOUT`)
3. `.grizzly.toml` in current directory 3. `.grizzly.toml` in current directory
4. `~/.config/grizzly/config.toml` 4. `~/.config/grizzly/config.toml`
Example `~/.config/grizzly/config.toml`: Example `~/.config/grizzly/config.toml`:
```toml ```toml
token_file = "~/.config/grizzly/token" token_file = "~/.config/grizzly/token"
callback_url = "http://127.0.0.1:42123/success" callback_url = "http://127.0.0.1:42123/success"

View File

@@ -2,7 +2,32 @@
name: bird name: bird
description: X/Twitter CLI for reading, searching, posting, and engagement via cookies. description: X/Twitter CLI for reading, searching, posting, and engagement via cookies.
homepage: https://bird.fast homepage: https://bird.fast
metadata: {"openclaw":{"emoji":"🐦","requires":{"bins":["bird"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/bird","bins":["bird"],"label":"Install bird (brew)","os":["darwin"]},{"id":"npm","kind":"node","package":"@steipete/bird","bins":["bird"],"label":"Install bird (npm)"}]}} metadata:
{
"openclaw":
{
"emoji": "🐦",
"requires": { "bins": ["bird"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/bird",
"bins": ["bird"],
"label": "Install bird (brew)",
"os": ["darwin"],
},
{
"id": "npm",
"kind": "node",
"package": "@steipete/bird",
"bins": ["bird"],
"label": "Install bird (npm)",
},
],
},
}
--- ---
# bird 🐦 # bird 🐦
@@ -174,7 +199,7 @@ bird replies <id> --all --delay 1000 # Delay between pages (ms)
cookieSource: ["chrome"], cookieSource: ["chrome"],
chromeProfileDir: "/path/to/Arc/Profile", chromeProfileDir: "/path/to/Arc/Profile",
timeoutMs: 20000, timeoutMs: 20000,
quoteDepth: 1 quoteDepth: 1,
} }
``` ```
@@ -183,11 +208,13 @@ Environment variables: `BIRD_TIMEOUT_MS`, `BIRD_COOKIE_TIMEOUT_MS`, `BIRD_QUOTE_
## Troubleshooting ## Troubleshooting
### Query IDs stale (404 errors) ### Query IDs stale (404 errors)
```bash ```bash
bird query-ids --fresh bird query-ids --fresh
``` ```
### Cookie extraction fails ### Cookie extraction fails
- Check browser is logged into X - Check browser is logged into X
- Try different `--cookie-source` - Try different `--cookie-source`
- For Arc/Brave: use `--chrome-profile-dir` - For Arc/Brave: use `--chrome-profile-dir`

View File

@@ -2,7 +2,24 @@
name: blogwatcher name: blogwatcher
description: Monitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI. description: Monitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI.
homepage: https://github.com/Hyaxia/blogwatcher homepage: https://github.com/Hyaxia/blogwatcher
metadata: {"openclaw":{"emoji":"📰","requires":{"bins":["blogwatcher"]},"install":[{"id":"go","kind":"go","module":"github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest","bins":["blogwatcher"],"label":"Install blogwatcher (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "📰",
"requires": { "bins": ["blogwatcher"] },
"install":
[
{
"id": "go",
"kind": "go",
"module": "github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest",
"bins": ["blogwatcher"],
"label": "Install blogwatcher (go)",
},
],
},
}
--- ---
# blogwatcher # blogwatcher
@@ -10,12 +27,15 @@ metadata: {"openclaw":{"emoji":"📰","requires":{"bins":["blogwatcher"]},"insta
Track blog and RSS/Atom feed updates with the `blogwatcher` CLI. Track blog and RSS/Atom feed updates with the `blogwatcher` CLI.
Install Install
- Go: `go install github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest` - Go: `go install github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest`
Quick start Quick start
- `blogwatcher --help` - `blogwatcher --help`
Common commands Common commands
- Add a blog: `blogwatcher add "My Blog" https://example.com` - Add a blog: `blogwatcher add "My Blog" https://example.com`
- List blogs: `blogwatcher blogs` - List blogs: `blogwatcher blogs`
- Scan for updates: `blogwatcher scan` - Scan for updates: `blogwatcher scan`
@@ -25,6 +45,7 @@ Common commands
- Remove a blog: `blogwatcher remove "My Blog"` - Remove a blog: `blogwatcher remove "My Blog"`
Example output Example output
``` ```
$ blogwatcher blogs $ blogwatcher blogs
Tracked blogs (1): Tracked blogs (1):
@@ -32,6 +53,7 @@ Tracked blogs (1):
xkcd xkcd
URL: https://xkcd.com URL: https://xkcd.com
``` ```
``` ```
$ blogwatcher scan $ blogwatcher scan
Scanning 1 blog(s)... Scanning 1 blog(s)...
@@ -43,4 +65,5 @@ Found 4 new article(s) total!
``` ```
Notes Notes
- Use `blogwatcher <command> --help` to discover flags and options. - Use `blogwatcher <command> --help` to discover flags and options.

View File

@@ -2,7 +2,24 @@
name: blucli name: blucli
description: BluOS CLI (blu) for discovery, playback, grouping, and volume. description: BluOS CLI (blu) for discovery, playback, grouping, and volume.
homepage: https://blucli.sh homepage: https://blucli.sh
metadata: {"openclaw":{"emoji":"🫐","requires":{"bins":["blu"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/blucli/cmd/blu@latest","bins":["blu"],"label":"Install blucli (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "🫐",
"requires": { "bins": ["blu"] },
"install":
[
{
"id": "go",
"kind": "go",
"module": "github.com/steipete/blucli/cmd/blu@latest",
"bins": ["blu"],
"label": "Install blucli (go)",
},
],
},
}
--- ---
# blucli (blu) # blucli (blu)
@@ -10,17 +27,20 @@ metadata: {"openclaw":{"emoji":"🫐","requires":{"bins":["blu"]},"install":[{"i
Use `blu` to control Bluesound/NAD players. Use `blu` to control Bluesound/NAD players.
Quick start Quick start
- `blu devices` (pick target) - `blu devices` (pick target)
- `blu --device <id> status` - `blu --device <id> status`
- `blu play|pause|stop` - `blu play|pause|stop`
- `blu volume set 15` - `blu volume set 15`
Target selection (in priority order) Target selection (in priority order)
- `--device <id|name|alias>` - `--device <id|name|alias>`
- `BLU_DEVICE` - `BLU_DEVICE`
- config default (if set) - config default (if set)
Common tasks Common tasks
- Grouping: `blu group status|add|remove` - Grouping: `blu group status|add|remove`
- TuneIn search/play: `blu tunein search "query"`, `blu tunein play "query"` - TuneIn search/play: `blu tunein search "query"`, `blu tunein play "query"`

View File

@@ -8,6 +8,7 @@ description: Build or update the BlueBubbles external channel plugin for OpenCla
Use this skill when working on the BlueBubbles channel plugin. Use this skill when working on the BlueBubbles channel plugin.
## Layout ## Layout
- Extension package: `extensions/bluebubbles/` (entry: `index.ts`). - Extension package: `extensions/bluebubbles/` (entry: `index.ts`).
- Channel implementation: `extensions/bluebubbles/src/channel.ts`. - Channel implementation: `extensions/bluebubbles/src/channel.ts`.
- Webhook handling: `extensions/bluebubbles/src/monitor.ts` (register via `api.registerHttpHandler`). - Webhook handling: `extensions/bluebubbles/src/monitor.ts` (register via `api.registerHttpHandler`).
@@ -16,6 +17,7 @@ Use this skill when working on the BlueBubbles channel plugin.
- Catalog entry for onboarding: `src/channels/plugins/catalog.ts`. - Catalog entry for onboarding: `src/channels/plugins/catalog.ts`.
## Internal helpers (use these, not raw API calls) ## Internal helpers (use these, not raw API calls)
- `probeBlueBubbles` in `extensions/bluebubbles/src/probe.ts` for health checks. - `probeBlueBubbles` in `extensions/bluebubbles/src/probe.ts` for health checks.
- `sendMessageBlueBubbles` in `extensions/bluebubbles/src/send.ts` for text delivery. - `sendMessageBlueBubbles` in `extensions/bluebubbles/src/send.ts` for text delivery.
- `resolveChatGuidForTarget` in `extensions/bluebubbles/src/send.ts` for chat lookup. - `resolveChatGuidForTarget` in `extensions/bluebubbles/src/send.ts` for chat lookup.
@@ -25,6 +27,7 @@ Use this skill when working on the BlueBubbles channel plugin.
- `buildBlueBubblesApiUrl` + `blueBubblesFetchWithTimeout` in `extensions/bluebubbles/src/types.ts` for shared REST plumbing. - `buildBlueBubblesApiUrl` + `blueBubblesFetchWithTimeout` in `extensions/bluebubbles/src/types.ts` for shared REST plumbing.
## Webhooks ## Webhooks
- BlueBubbles posts JSON to the gateway HTTP server. - BlueBubbles posts JSON to the gateway HTTP server.
- Normalize sender/chat IDs defensively (payloads vary by version). - Normalize sender/chat IDs defensively (payloads vary by version).
- Skip messages marked as from self. - Skip messages marked as from self.
@@ -32,8 +35,10 @@ Use this skill when working on the BlueBubbles channel plugin.
- For attachments/stickers, use `<media:...>` placeholders when text is empty and attach media paths via `MediaUrl(s)` in the inbound context. - For attachments/stickers, use `<media:...>` placeholders when text is empty and attach media paths via `MediaUrl(s)` in the inbound context.
## Config (core) ## Config (core)
- `channels.bluebubbles.serverUrl` (base URL), `channels.bluebubbles.password`, `channels.bluebubbles.webhookPath`. - `channels.bluebubbles.serverUrl` (base URL), `channels.bluebubbles.password`, `channels.bluebubbles.webhookPath`.
- Action gating: `channels.bluebubbles.actions.reactions` (default true). - Action gating: `channels.bluebubbles.actions.reactions` (default true).
## Message tool notes ## Message tool notes
- **Reactions:** The `react` action requires a `target` (phone number or chat identifier) in addition to `messageId`. Example: `action=react target=+15551234567 messageId=ABC123 emoji=❤️` - **Reactions:** The `react` action requires a `target` (phone number or chat identifier) in addition to `messageId`. Example: `action=react target=+15551234567 messageId=ABC123 emoji=❤️`

View File

@@ -2,7 +2,24 @@
name: camsnap name: camsnap
description: Capture frames or clips from RTSP/ONVIF cameras. description: Capture frames or clips from RTSP/ONVIF cameras.
homepage: https://camsnap.ai homepage: https://camsnap.ai
metadata: {"openclaw":{"emoji":"📸","requires":{"bins":["camsnap"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/camsnap","bins":["camsnap"],"label":"Install camsnap (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "📸",
"requires": { "bins": ["camsnap"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/camsnap",
"bins": ["camsnap"],
"label": "Install camsnap (brew)",
},
],
},
}
--- ---
# camsnap # camsnap
@@ -10,10 +27,12 @@ metadata: {"openclaw":{"emoji":"📸","requires":{"bins":["camsnap"]},"install":
Use `camsnap` to grab snapshots, clips, or motion events from configured cameras. Use `camsnap` to grab snapshots, clips, or motion events from configured cameras.
Setup Setup
- Config file: `~/.config/camsnap/config.yaml` - Config file: `~/.config/camsnap/config.yaml`
- Add camera: `camsnap add --name kitchen --host 192.168.0.10 --user user --pass pass` - Add camera: `camsnap add --name kitchen --host 192.168.0.10 --user user --pass pass`
Common commands Common commands
- Discover: `camsnap discover --info` - Discover: `camsnap discover --info`
- Snapshot: `camsnap snap kitchen --out shot.jpg` - Snapshot: `camsnap snap kitchen --out shot.jpg`
- Clip: `camsnap clip kitchen --dur 5s --out clip.mp4` - Clip: `camsnap clip kitchen --dur 5s --out clip.mp4`
@@ -21,5 +40,6 @@ Common commands
- Doctor: `camsnap doctor --probe` - Doctor: `camsnap doctor --probe`
Notes Notes
- Requires `ffmpeg` on PATH. - Requires `ffmpeg` on PATH.
- Prefer a short test capture before longer clips. - Prefer a short test capture before longer clips.

View File

@@ -5,6 +5,7 @@ Display HTML content on connected OpenClaw nodes (Mac app, iOS, Android).
## Overview ## Overview
The canvas tool lets you present web content on any connected node's canvas view. Great for: The canvas tool lets you present web content on any connected node's canvas view. Great for:
- Displaying games, visualizations, dashboards - Displaying games, visualizations, dashboards
- Showing generated HTML content - Showing generated HTML content
- Interactive demos - Interactive demos
@@ -29,14 +30,15 @@ The canvas tool lets you present web content on any connected node's canvas view
The canvas host server binds based on `gateway.bind` setting: The canvas host server binds based on `gateway.bind` setting:
| Bind Mode | Server Binds To | Canvas URL Uses | | Bind Mode | Server Binds To | Canvas URL Uses |
|-----------|-----------------|-----------------| | ---------- | ------------------- | -------------------------- |
| `loopback` | 127.0.0.1 | localhost (local only) | | `loopback` | 127.0.0.1 | localhost (local only) |
| `lan` | LAN interface | LAN IP address | | `lan` | LAN interface | LAN IP address |
| `tailnet` | Tailscale interface | Tailscale hostname | | `tailnet` | Tailscale interface | Tailscale hostname |
| `auto` | Best available | Tailscale > LAN > loopback | | `auto` | Best available | Tailscale > LAN > loopback |
**Key insight:** The `canvasHostHostForBridge` is derived from `bridgeHost`. When bound to Tailscale, nodes receive URLs like: **Key insight:** The `canvasHostHostForBridge` is derived from `bridgeHost`. When bound to Tailscale, nodes receive URLs like:
``` ```
http://<tailscale-hostname>:18793/__moltbot__/canvas/<file>.html http://<tailscale-hostname>:18793/__moltbot__/canvas/<file>.html
``` ```
@@ -45,13 +47,13 @@ This is why localhost URLs don't work - the node receives the Tailscale hostname
## Actions ## Actions
| Action | Description | | Action | Description |
|--------|-------------| | ---------- | ------------------------------------ |
| `present` | Show canvas with optional target URL | | `present` | Show canvas with optional target URL |
| `hide` | Hide the canvas | | `hide` | Hide the canvas |
| `navigate` | Navigate to a new URL | | `navigate` | Navigate to a new URL |
| `eval` | Execute JavaScript in the canvas | | `eval` | Execute JavaScript in the canvas |
| `snapshot` | Capture screenshot of canvas | | `snapshot` | Capture screenshot of canvas |
## Configuration ## Configuration
@@ -74,6 +76,7 @@ In `~/.openclaw/openclaw.json`:
### Live Reload ### Live Reload
When `liveReload: true` (default), the canvas host: When `liveReload: true` (default), the canvas host:
- Watches the root directory for changes (via chokidar) - Watches the root directory for changes (via chokidar)
- Injects a WebSocket client into HTML files - Injects a WebSocket client into HTML files
- Automatically reloads connected canvases when files change - Automatically reloads connected canvases when files change
@@ -101,15 +104,18 @@ HTML
### 2. Find your canvas host URL ### 2. Find your canvas host URL
Check how your gateway is bound: Check how your gateway is bound:
```bash ```bash
cat ~/.openclaw/openclaw.json | jq '.gateway.bind' cat ~/.openclaw/openclaw.json | jq '.gateway.bind'
``` ```
Then construct the URL: Then construct the URL:
- **loopback**: `http://127.0.0.1:18793/__moltbot__/canvas/<file>.html` - **loopback**: `http://127.0.0.1:18793/__moltbot__/canvas/<file>.html`
- **lan/tailnet/auto**: `http://<hostname>:18793/__moltbot__/canvas/<file>.html` - **lan/tailnet/auto**: `http://<hostname>:18793/__moltbot__/canvas/<file>.html`
Find your Tailscale hostname: Find your Tailscale hostname:
```bash ```bash
tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//' tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//'
``` ```
@@ -129,6 +135,7 @@ canvas action:present node:<node-id> target:<full-url>
``` ```
**Example:** **Example:**
``` ```
canvas action:present node:mac-63599bc4-b54d-4392-9048-b97abd58343a target:http://peters-mac-studio-1.sheep-coho.ts.net:18793/__moltbot__/canvas/snake.html canvas action:present node:mac-63599bc4-b54d-4392-9048-b97abd58343a target:http://peters-mac-studio-1.sheep-coho.ts.net:18793/__moltbot__/canvas/snake.html
``` ```
@@ -148,6 +155,7 @@ canvas action:hide node:<node-id>
**Cause:** URL mismatch between server bind and node expectation. **Cause:** URL mismatch between server bind and node expectation.
**Debug steps:** **Debug steps:**
1. Check server bind: `cat ~/.openclaw/openclaw.json | jq '.gateway.bind'` 1. Check server bind: `cat ~/.openclaw/openclaw.json | jq '.gateway.bind'`
2. Check what port canvas is on: `lsof -i :18793` 2. Check what port canvas is on: `lsof -i :18793`
3. Test URL directly: `curl http://<hostname>:18793/__moltbot__/canvas/<file>.html` 3. Test URL directly: `curl http://<hostname>:18793/__moltbot__/canvas/<file>.html`
@@ -165,6 +173,7 @@ Node is offline. Use `openclaw nodes list` to find online nodes.
### Content not updating ### Content not updating
If live reload isn't working: If live reload isn't working:
1. Check `liveReload: true` in config 1. Check `liveReload: true` in config
2. Ensure file is in the canvas root directory 2. Ensure file is in the canvas root directory
3. Check for watcher errors in logs 3. Check for watcher errors in logs

View File

@@ -1,34 +1,55 @@
--- ---
name: clawhub name: clawhub
description: Use the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com. Use when you need to fetch new skills on the fly, sync installed skills to latest or a specific version, or publish new/updated skill folders with the npm-installed clawhub CLI. description: Use the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com. Use when you need to fetch new skills on the fly, sync installed skills to latest or a specific version, or publish new/updated skill folders with the npm-installed clawhub CLI.
metadata: {"openclaw":{"requires":{"bins":["clawhub"]},"install":[{"id":"node","kind":"node","package":"clawhub","bins":["clawhub"],"label":"Install ClawHub CLI (npm)"}]}} metadata:
{
"openclaw":
{
"requires": { "bins": ["clawhub"] },
"install":
[
{
"id": "node",
"kind": "node",
"package": "clawhub",
"bins": ["clawhub"],
"label": "Install ClawHub CLI (npm)",
},
],
},
}
--- ---
# ClawHub CLI # ClawHub CLI
Install Install
```bash ```bash
npm i -g clawhub npm i -g clawhub
``` ```
Auth (publish) Auth (publish)
```bash ```bash
clawhub login clawhub login
clawhub whoami clawhub whoami
``` ```
Search Search
```bash ```bash
clawhub search "postgres backups" clawhub search "postgres backups"
``` ```
Install Install
```bash ```bash
clawhub install my-skill clawhub install my-skill
clawhub install my-skill --version 1.2.3 clawhub install my-skill --version 1.2.3
``` ```
Update (hash-based match + upgrade) Update (hash-based match + upgrade)
```bash ```bash
clawhub update my-skill clawhub update my-skill
clawhub update my-skill --version 1.2.3 clawhub update my-skill --version 1.2.3
@@ -38,16 +59,19 @@ clawhub update --all --no-input --force
``` ```
List List
```bash ```bash
clawhub list clawhub list
``` ```
Publish Publish
```bash ```bash
clawhub publish ./my-skill --slug my-skill --name "My Skill" --version 1.2.0 --changelog "Fixes + docs" clawhub publish ./my-skill --slug my-skill --name "My Skill" --version 1.2.0 --changelog "Fixes + docs"
``` ```
Notes Notes
- Default registry: https://clawhub.com (override with CLAWHUB_REGISTRY or --registry) - Default registry: https://clawhub.com (override with CLAWHUB_REGISTRY or --registry)
- Default workdir: cwd (falls back to OpenClaw workspace); install dir: ./skills (override with --workdir / --dir / CLAWHUB_WORKDIR) - Default workdir: cwd (falls back to OpenClaw workspace); install dir: ./skills (override with --workdir / --dir / CLAWHUB_WORKDIR)
- Update command hashes local files, resolves matching version, and upgrades to latest unless --version is set - Update command hashes local files, resolves matching version, and upgrades to latest unless --version is set

View File

@@ -1,7 +1,10 @@
--- ---
name: coding-agent name: coding-agent
description: Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control. description: Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control.
metadata: {"openclaw":{"emoji":"🧩","requires":{"anyBins":["claude","codex","opencode","pi"]}}} metadata:
{
"openclaw": { "emoji": "🧩", "requires": { "anyBins": ["claude", "codex", "opencode", "pi"] } },
}
--- ---
# Coding Agent (bash-first) # Coding Agent (bash-first)
@@ -24,27 +27,27 @@ bash command:"codex exec 'Your prompt'"
### Bash Tool Parameters ### Bash Tool Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
|-----------|------|-------------| | ------------ | ------- | --------------------------------------------------------------------------- |
| `command` | string | The shell command to run | | `command` | string | The shell command to run |
| `pty` | boolean | **Use for coding agents!** Allocates a pseudo-terminal for interactive CLIs | | `pty` | boolean | **Use for coding agents!** Allocates a pseudo-terminal for interactive CLIs |
| `workdir` | string | Working directory (agent sees only this folder's context) | | `workdir` | string | Working directory (agent sees only this folder's context) |
| `background` | boolean | Run in background, returns sessionId for monitoring | | `background` | boolean | Run in background, returns sessionId for monitoring |
| `timeout` | number | Timeout in seconds (kills process on expiry) | | `timeout` | number | Timeout in seconds (kills process on expiry) |
| `elevated` | boolean | Run on host instead of sandbox (if allowed) | | `elevated` | boolean | Run on host instead of sandbox (if allowed) |
### Process Tool Actions (for background sessions) ### Process Tool Actions (for background sessions)
| Action | Description | | Action | Description |
|--------|-------------| | ----------- | ---------------------------------------------------- |
| `list` | List all running/recent sessions | | `list` | List all running/recent sessions |
| `poll` | Check if session is still running | | `poll` | Check if session is still running |
| `log` | Get session output (with optional offset/limit) | | `log` | Get session output (with optional offset/limit) |
| `write` | Send raw data to stdin | | `write` | Send raw data to stdin |
| `submit` | Send data + newline (like typing and pressing Enter) | | `submit` | Send data + newline (like typing and pressing Enter) |
| `send-keys` | Send key tokens or hex bytes | | `send-keys` | Send key tokens or hex bytes |
| `paste` | Paste text (with optional bracketed mode) | | `paste` | Paste text (with optional bracketed mode) |
| `kill` | Terminate the session | | `kill` | Terminate the session |
--- ---
@@ -99,13 +102,14 @@ process action:kill sessionId:XXX
### Flags ### Flags
| Flag | Effect | | Flag | Effect |
|------|--------| | --------------- | -------------------------------------------------- |
| `exec "prompt"` | One-shot execution, exits when done | | `exec "prompt"` | One-shot execution, exits when done |
| `--full-auto` | Sandboxed but auto-approves in workspace | | `--full-auto` | Sandboxed but auto-approves in workspace |
| `--yolo` | NO sandbox, NO approvals (fastest, most dangerous) | | `--yolo` | NO sandbox, NO approvals (fastest, most dangerous) |
### Building/Creating ### Building/Creating
```bash ```bash
# Quick one-shot (auto-approves) - remember PTY! # Quick one-shot (auto-approves) - remember PTY!
bash pty:true workdir:~/project command:"codex exec --full-auto 'Build a dark mode toggle'" bash pty:true workdir:~/project command:"codex exec --full-auto 'Build a dark mode toggle'"
@@ -133,6 +137,7 @@ bash pty:true workdir:/tmp/pr-130-review command:"codex review --base main"
``` ```
### Batch PR Reviews (parallel army!) ### Batch PR Reviews (parallel army!)
```bash ```bash
# Fetch all PR refs first # Fetch all PR refs first
git fetch origin '+refs/pull/*/head:refs/remotes/origin/pr/*' git fetch origin '+refs/pull/*/head:refs/remotes/origin/pr/*'
@@ -259,6 +264,7 @@ openclaw gateway wake --text "Done: [brief summary of what was built]" --mode no
``` ```
**Example:** **Example:**
```bash ```bash
bash pty:true workdir:~/project background:true command:"codex --yolo exec 'Build a REST API for todos. bash pty:true workdir:~/project background:true command:"codex --yolo exec 'Build a REST API for todos.
@@ -275,4 +281,4 @@ This triggers an immediate wake event — Skippy gets pinged in seconds, not 10
- **Git repo required:** Codex won't run outside a git directory. Use `mktemp -d && git init` for scratch work. - **Git repo required:** Codex won't run outside a git directory. Use `mktemp -d && git init` for scratch work.
- **exec is your friend:** `codex exec "prompt"` runs and exits cleanly - perfect for one-shots. - **exec is your friend:** `codex exec "prompt"` runs and exits cleanly - perfect for one-shots.
- **submit vs write:** Use `submit` to send input + Enter, `write` for raw data without newline. - **submit vs write:** Use `submit` to send input + Enter, `write` for raw data without newline.
- **Sass works:** Codex responds well to playful prompts. Asked it to write a haiku about being second fiddle to a space lobster, got: *"Second chair, I code / Space lobster sets the tempo / Keys glow, I follow"* 🦞 - **Sass works:** Codex responds well to playful prompts. Asked it to write a haiku about being second fiddle to a space lobster, got: _"Second chair, I code / Space lobster sets the tempo / Keys glow, I follow"_ 🦞

View File

@@ -131,6 +131,7 @@ Message context lines include `discord message id` and `channel` fields you can
## Action gating ## Action gating
Use `discord.actions.*` to disable action groups: Use `discord.actions.*` to disable action groups:
- `reactions` (react + reactions list + emojiList) - `reactions` (react + reactions list + emojiList)
- `stickers`, `polls`, `permissions`, `messages`, `threads`, `pins`, `search` - `stickers`, `polls`, `permissions`, `messages`, `threads`, `pins`, `search`
- `emojiUploads`, `stickerUploads` - `emojiUploads`, `stickerUploads`
@@ -138,6 +139,7 @@ Use `discord.actions.*` to disable action groups:
- `roles` (role add/remove, default `false`) - `roles` (role add/remove, default `false`)
- `channels` (channel/category create/edit/delete/move, default `false`) - `channels` (channel/category create/edit/delete/move, default `false`)
- `moderation` (timeout/kick/ban, default `false`) - `moderation` (timeout/kick/ban, default `false`)
### Read recent messages ### Read recent messages
```json ```json
@@ -435,6 +437,7 @@ Create, edit, delete, and move channels and categories. Enable via `discord.acti
**Keep it conversational!** Discord is a chat platform, not documentation. **Keep it conversational!** Discord is a chat platform, not documentation.
### Do ### Do
- Short, punchy messages (1-3 sentences ideal) - Short, punchy messages (1-3 sentences ideal)
- Multiple quick replies > one wall of text - Multiple quick replies > one wall of text
- Use emoji for tone/emphasis 🦞 - Use emoji for tone/emphasis 🦞
@@ -443,6 +446,7 @@ Create, edit, delete, and move channels and categories. Enable via `discord.acti
- Match the energy of the conversation - Match the energy of the conversation
### Don't ### Don't
- No markdown tables (Discord renders them as ugly raw `| text |`) - No markdown tables (Discord renders them as ugly raw `| text |`)
- No `## Headers` for casual chat (use **bold** or CAPS for emphasis) - No `## Headers` for casual chat (use **bold** or CAPS for emphasis)
- Avoid multi-paragraph essays - Avoid multi-paragraph essays
@@ -450,6 +454,7 @@ Create, edit, delete, and move channels and categories. Enable via `discord.acti
- Skip the "I'd be happy to help!" fluff - Skip the "I'd be happy to help!" fluff
### Formatting that works ### Formatting that works
- **bold** for emphasis - **bold** for emphasis
- `code` for technical terms - `code` for technical terms
- Lists for multiple items - Lists for multiple items
@@ -459,6 +464,7 @@ Create, edit, delete, and move channels and categories. Enable via `discord.acti
### Example transformations ### Example transformations
❌ Bad: ❌ Bad:
``` ```
I'd be happy to help with that! Here's a comprehensive overview of the versioning strategies available: I'd be happy to help with that! Here's a comprehensive overview of the versioning strategies available:
@@ -470,6 +476,7 @@ CalVer uses date-based versions like...
``` ```
✅ Good: ✅ Good:
``` ```
versioning options: semver (1.2.3), calver (2026.01.04), or yolo (`latest` forever). what fits your release cadence? versioning options: semver (1.2.3), calver (2026.01.04), or yolo (`latest` forever). what fits your release cadence?
``` ```

View File

@@ -2,7 +2,24 @@
name: eightctl name: eightctl
description: Control Eight Sleep pods (status, temperature, alarms, schedules). description: Control Eight Sleep pods (status, temperature, alarms, schedules).
homepage: https://eightctl.sh homepage: https://eightctl.sh
metadata: {"openclaw":{"emoji":"🎛️","requires":{"bins":["eightctl"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/eightctl/cmd/eightctl@latest","bins":["eightctl"],"label":"Install eightctl (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "🎛️",
"requires": { "bins": ["eightctl"] },
"install":
[
{
"id": "go",
"kind": "go",
"module": "github.com/steipete/eightctl/cmd/eightctl@latest",
"bins": ["eightctl"],
"label": "Install eightctl (go)",
},
],
},
}
--- ---
# eightctl # eightctl
@@ -10,20 +27,24 @@ metadata: {"openclaw":{"emoji":"🎛️","requires":{"bins":["eightctl"]},"insta
Use `eightctl` for Eight Sleep pod control. Requires auth. Use `eightctl` for Eight Sleep pod control. Requires auth.
Auth Auth
- Config: `~/.config/eightctl/config.yaml` - Config: `~/.config/eightctl/config.yaml`
- Env: `EIGHTCTL_EMAIL`, `EIGHTCTL_PASSWORD` - Env: `EIGHTCTL_EMAIL`, `EIGHTCTL_PASSWORD`
Quick start Quick start
- `eightctl status` - `eightctl status`
- `eightctl on|off` - `eightctl on|off`
- `eightctl temp 20` - `eightctl temp 20`
Common tasks Common tasks
- Alarms: `eightctl alarm list|create|dismiss` - Alarms: `eightctl alarm list|create|dismiss`
- Schedules: `eightctl schedule list|create|update` - Schedules: `eightctl schedule list|create|update`
- Audio: `eightctl audio state|play|pause` - Audio: `eightctl audio state|play|pause`
- Base: `eightctl base info|angle` - Base: `eightctl base info|angle`
Notes Notes
- API is unofficial and rate-limited; avoid repeated logins. - API is unofficial and rate-limited; avoid repeated logins.
- Confirm before changing temperature or alarms. - Confirm before changing temperature or alarms.

View File

@@ -10,32 +10,39 @@ metadata: {"openclaw":{"emoji":"🥡","requires":{"bins":["ordercli"]},"install"
Goal: reorder a previous Foodora order safely (preview first; confirm only on explicit user “yes/confirm/place the order”). Goal: reorder a previous Foodora order safely (preview first; confirm only on explicit user “yes/confirm/place the order”).
Hard safety rules Hard safety rules
- Never run `ordercli foodora reorder ... --confirm` unless user explicitly confirms placing the order. - Never run `ordercli foodora reorder ... --confirm` unless user explicitly confirms placing the order.
- Prefer preview-only steps first; show what will happen; ask for confirmation. - Prefer preview-only steps first; show what will happen; ask for confirmation.
- If user is unsure: stop at preview and ask questions. - If user is unsure: stop at preview and ask questions.
Setup (once) Setup (once)
- Country: `ordercli foodora countries``ordercli foodora config set --country AT` - Country: `ordercli foodora countries``ordercli foodora config set --country AT`
- Login (password): `ordercli foodora login --email you@example.com --password-stdin` - Login (password): `ordercli foodora login --email you@example.com --password-stdin`
- Login (no password, preferred): `ordercli foodora session chrome --url https://www.foodora.at/ --profile "Default"` - Login (no password, preferred): `ordercli foodora session chrome --url https://www.foodora.at/ --profile "Default"`
Find what to reorder Find what to reorder
- Recent list: `ordercli foodora history --limit 10` - Recent list: `ordercli foodora history --limit 10`
- Details: `ordercli foodora history show <orderCode>` - Details: `ordercli foodora history show <orderCode>`
- If needed (machine-readable): `ordercli foodora history show <orderCode> --json` - If needed (machine-readable): `ordercli foodora history show <orderCode> --json`
Preview reorder (no cart changes) Preview reorder (no cart changes)
- `ordercli foodora reorder <orderCode>` - `ordercli foodora reorder <orderCode>`
Place reorder (cart change; explicit confirmation required) Place reorder (cart change; explicit confirmation required)
- Confirm first, then run: `ordercli foodora reorder <orderCode> --confirm` - Confirm first, then run: `ordercli foodora reorder <orderCode> --confirm`
- Multiple addresses? Ask user for the right `--address-id` (take from their Foodora account / prior order data) and run: - Multiple addresses? Ask user for the right `--address-id` (take from their Foodora account / prior order data) and run:
- `ordercli foodora reorder <orderCode> --confirm --address-id <id>` - `ordercli foodora reorder <orderCode> --confirm --address-id <id>`
Track the order Track the order
- ETA/status (active list): `ordercli foodora orders` - ETA/status (active list): `ordercli foodora orders`
- Live updates: `ordercli foodora orders --watch` - Live updates: `ordercli foodora orders --watch`
- Single order detail: `ordercli foodora order <orderCode>` - Single order detail: `ordercli foodora order <orderCode>`
Debug / safe testing Debug / safe testing
- Use a throwaway config: `ordercli --config /tmp/ordercli.json ...` - Use a throwaway config: `ordercli --config /tmp/ordercli.json ...`

View File

@@ -2,7 +2,24 @@
name: gemini name: gemini
description: Gemini CLI for one-shot Q&A, summaries, and generation. description: Gemini CLI for one-shot Q&A, summaries, and generation.
homepage: https://ai.google.dev/ homepage: https://ai.google.dev/
metadata: {"openclaw":{"emoji":"♊️","requires":{"bins":["gemini"]},"install":[{"id":"brew","kind":"brew","formula":"gemini-cli","bins":["gemini"],"label":"Install Gemini CLI (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "♊️",
"requires": { "bins": ["gemini"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "gemini-cli",
"bins": ["gemini"],
"label": "Install Gemini CLI (brew)",
},
],
},
}
--- ---
# Gemini CLI # Gemini CLI
@@ -10,14 +27,17 @@ metadata: {"openclaw":{"emoji":"♊️","requires":{"bins":["gemini"]},"install"
Use Gemini in one-shot mode with a positional prompt (avoid interactive mode). Use Gemini in one-shot mode with a positional prompt (avoid interactive mode).
Quick start Quick start
- `gemini "Answer this question..."` - `gemini "Answer this question..."`
- `gemini --model <name> "Prompt..."` - `gemini --model <name> "Prompt..."`
- `gemini --output-format json "Return JSON"` - `gemini --output-format json "Return JSON"`
Extensions Extensions
- List: `gemini --list-extensions` - List: `gemini --list-extensions`
- Manage: `gemini extensions <command>` - Manage: `gemini extensions <command>`
Notes Notes
- If auth is required, run `gemini` once interactively and follow the login flow. - If auth is required, run `gemini` once interactively and follow the login flow.
- Avoid `--yolo` for safety. - Avoid `--yolo` for safety.

View File

@@ -2,7 +2,31 @@
name: gifgrep name: gifgrep
description: Search GIF providers with CLI/TUI, download results, and extract stills/sheets. description: Search GIF providers with CLI/TUI, download results, and extract stills/sheets.
homepage: https://gifgrep.com homepage: https://gifgrep.com
metadata: {"openclaw":{"emoji":"🧲","requires":{"bins":["gifgrep"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/gifgrep","bins":["gifgrep"],"label":"Install gifgrep (brew)"},{"id":"go","kind":"go","module":"github.com/steipete/gifgrep/cmd/gifgrep@latest","bins":["gifgrep"],"label":"Install gifgrep (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "🧲",
"requires": { "bins": ["gifgrep"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/gifgrep",
"bins": ["gifgrep"],
"label": "Install gifgrep (brew)",
},
{
"id": "go",
"kind": "go",
"module": "github.com/steipete/gifgrep/cmd/gifgrep@latest",
"bins": ["gifgrep"],
"label": "Install gifgrep (go)",
},
],
},
}
--- ---
# gifgrep # gifgrep
@@ -10,9 +34,11 @@ metadata: {"openclaw":{"emoji":"🧲","requires":{"bins":["gifgrep"]},"install":
Use `gifgrep` to search GIF providers (Tenor/Giphy), browse in a TUI, download results, and extract stills or sheets. Use `gifgrep` to search GIF providers (Tenor/Giphy), browse in a TUI, download results, and extract stills or sheets.
GIF-Grab (gifgrep workflow) GIF-Grab (gifgrep workflow)
- Search → preview → download → extract (still/sheet) for fast review and sharing. - Search → preview → download → extract (still/sheet) for fast review and sharing.
Quick start Quick start
- `gifgrep cats --max 5` - `gifgrep cats --max 5`
- `gifgrep cats --format url | head -n 5` - `gifgrep cats --format url | head -n 5`
- `gifgrep search --json cats | jq '.[0].url'` - `gifgrep search --json cats | jq '.[0].url'`
@@ -20,28 +46,34 @@ Quick start
- `gifgrep cats --download --max 1 --format url` - `gifgrep cats --download --max 1 --format url`
TUI + previews TUI + previews
- TUI: `gifgrep tui "query"` - TUI: `gifgrep tui "query"`
- CLI still previews: `--thumbs` (Kitty/Ghostty only; still frame) - CLI still previews: `--thumbs` (Kitty/Ghostty only; still frame)
Download + reveal Download + reveal
- `--download` saves to `~/Downloads` - `--download` saves to `~/Downloads`
- `--reveal` shows the last download in Finder - `--reveal` shows the last download in Finder
Stills + sheets Stills + sheets
- `gifgrep still ./clip.gif --at 1.5s -o still.png` - `gifgrep still ./clip.gif --at 1.5s -o still.png`
- `gifgrep sheet ./clip.gif --frames 9 --cols 3 -o sheet.png` - `gifgrep sheet ./clip.gif --frames 9 --cols 3 -o sheet.png`
- Sheets = single PNG grid of sampled frames (great for quick review, docs, PRs, chat). - Sheets = single PNG grid of sampled frames (great for quick review, docs, PRs, chat).
- Tune: `--frames` (count), `--cols` (grid width), `--padding` (spacing). - Tune: `--frames` (count), `--cols` (grid width), `--padding` (spacing).
Providers Providers
- `--source auto|tenor|giphy` - `--source auto|tenor|giphy`
- `GIPHY_API_KEY` required for `--source giphy` - `GIPHY_API_KEY` required for `--source giphy`
- `TENOR_API_KEY` optional (Tenor demo key used if unset) - `TENOR_API_KEY` optional (Tenor demo key used if unset)
Output Output
- `--json` prints an array of results (`id`, `title`, `url`, `preview_url`, `tags`, `width`, `height`) - `--json` prints an array of results (`id`, `title`, `url`, `preview_url`, `tags`, `width`, `height`)
- `--format` for pipe-friendly fields (e.g., `url`) - `--format` for pipe-friendly fields (e.g., `url`)
Environment tweaks Environment tweaks
- `GIFGREP_SOFTWARE_ANIM=1` to force software animation - `GIFGREP_SOFTWARE_ANIM=1` to force software animation
- `GIFGREP_CELL_ASPECT=0.5` to tweak preview geometry - `GIFGREP_CELL_ASPECT=0.5` to tweak preview geometry

View File

@@ -1,7 +1,31 @@
--- ---
name: github name: github
description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries." description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries."
metadata: {"openclaw":{"emoji":"🐙","requires":{"bins":["gh"]},"install":[{"id":"brew","kind":"brew","formula":"gh","bins":["gh"],"label":"Install GitHub CLI (brew)"},{"id":"apt","kind":"apt","package":"gh","bins":["gh"],"label":"Install GitHub CLI (apt)"}]}} metadata:
{
"openclaw":
{
"emoji": "🐙",
"requires": { "bins": ["gh"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "gh",
"bins": ["gh"],
"label": "Install GitHub CLI (brew)",
},
{
"id": "apt",
"kind": "apt",
"package": "gh",
"bins": ["gh"],
"label": "Install GitHub CLI (apt)",
},
],
},
}
--- ---
# GitHub Skill # GitHub Skill
@@ -11,21 +35,25 @@ Use the `gh` CLI to interact with GitHub. Always specify `--repo owner/repo` whe
## Pull Requests ## Pull Requests
Check CI status on a PR: Check CI status on a PR:
```bash ```bash
gh pr checks 55 --repo owner/repo gh pr checks 55 --repo owner/repo
``` ```
List recent workflow runs: List recent workflow runs:
```bash ```bash
gh run list --repo owner/repo --limit 10 gh run list --repo owner/repo --limit 10
``` ```
View a run and see which steps failed: View a run and see which steps failed:
```bash ```bash
gh run view <run-id> --repo owner/repo gh run view <run-id> --repo owner/repo
``` ```
View logs for failed steps only: View logs for failed steps only:
```bash ```bash
gh run view <run-id> --repo owner/repo --log-failed gh run view <run-id> --repo owner/repo --log-failed
``` ```
@@ -35,13 +63,14 @@ gh run view <run-id> --repo owner/repo --log-failed
The `gh api` command is useful for accessing data not available through other subcommands. The `gh api` command is useful for accessing data not available through other subcommands.
Get PR with specific fields: Get PR with specific fields:
```bash ```bash
gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login' gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login'
``` ```
## JSON Output ## JSON Output
Most commands support `--json` for structured output. You can use `--jq` to filter: Most commands support `--json` for structured output. You can use `--jq` to filter:
```bash ```bash
gh issue list --repo owner/repo --json number,title --jq '.[] | "\(.number): \(.title)"' gh issue list --repo owner/repo --json number,title --jq '.[] | "\(.number): \(.title)"'

View File

@@ -2,7 +2,24 @@
name: gog name: gog
description: Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs. description: Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
homepage: https://gogcli.sh homepage: https://gogcli.sh
metadata: {"openclaw":{"emoji":"🎮","requires":{"bins":["gog"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/gogcli","bins":["gog"],"label":"Install gog (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🎮",
"requires": { "bins": ["gog"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/gogcli",
"bins": ["gog"],
"label": "Install gog (brew)",
},
],
},
}
--- ---
# gog # gog
@@ -10,11 +27,13 @@ metadata: {"openclaw":{"emoji":"🎮","requires":{"bins":["gog"]},"install":[{"i
Use `gog` for Gmail/Calendar/Drive/Contacts/Sheets/Docs. Requires OAuth setup. Use `gog` for Gmail/Calendar/Drive/Contacts/Sheets/Docs. Requires OAuth setup.
Setup (once) Setup (once)
- `gog auth credentials /path/to/client_secret.json` - `gog auth credentials /path/to/client_secret.json`
- `gog auth add you@gmail.com --services gmail,calendar,drive,contacts,docs,sheets` - `gog auth add you@gmail.com --services gmail,calendar,drive,contacts,docs,sheets`
- `gog auth list` - `gog auth list`
Common commands Common commands
- Gmail search: `gog gmail search 'newer_than:7d' --max 10` - Gmail search: `gog gmail search 'newer_than:7d' --max 10`
- Gmail messages search (per email, ignores threading): `gog gmail messages search "in:inbox from:ryanair.com" --max 20 --account you@example.com` - Gmail messages search (per email, ignores threading): `gog gmail messages search "in:inbox from:ryanair.com" --max 20 --account you@example.com`
- Gmail send (plain): `gog gmail send --to a@b.com --subject "Hi" --body "Hello"` - Gmail send (plain): `gog gmail send --to a@b.com --subject "Hi" --body "Hello"`
@@ -40,6 +59,7 @@ Common commands
- Docs cat: `gog docs cat <docId>` - Docs cat: `gog docs cat <docId>`
Calendar Colors Calendar Colors
- Use `gog calendar colors` to see all available event colors (IDs 1-11) - Use `gog calendar colors` to see all available event colors (IDs 1-11)
- Add colors to events with `--event-color <id>` flag - Add colors to events with `--event-color <id>` flag
- Event color IDs (from `gog calendar colors` output): - Event color IDs (from `gog calendar colors` output):
@@ -56,12 +76,14 @@ Calendar Colors
- 11: #dc2127 - 11: #dc2127
Email Formatting Email Formatting
- Prefer plain text. Use `--body-file` for multi-paragraph messages (or `--body-file -` for stdin). - Prefer plain text. Use `--body-file` for multi-paragraph messages (or `--body-file -` for stdin).
- Same `--body-file` pattern works for drafts and replies. - Same `--body-file` pattern works for drafts and replies.
- `--body` does not unescape `\n`. If you need inline newlines, use a heredoc or `$'Line 1\n\nLine 2'`. - `--body` does not unescape `\n`. If you need inline newlines, use a heredoc or `$'Line 1\n\nLine 2'`.
- Use `--body-html` only when you need rich formatting. - Use `--body-html` only when you need rich formatting.
- HTML tags: `<p>` for paragraphs, `<br>` for line breaks, `<strong>` for bold, `<em>` for italic, `<a href="url">` for links, `<ul>`/`<li>` for lists. - HTML tags: `<p>` for paragraphs, `<br>` for line breaks, `<strong>` for bold, `<em>` for italic, `<a href="url">` for links, `<ul>`/`<li>` for lists.
- Example (plain text via stdin): - Example (plain text via stdin):
```bash ```bash
gog gmail send --to recipient@example.com \ gog gmail send --to recipient@example.com \
--subject "Meeting Follow-up" \ --subject "Meeting Follow-up" \
@@ -76,6 +98,7 @@ Email Formatting
Your Name Your Name
EOF EOF
``` ```
- Example (HTML list): - Example (HTML list):
```bash ```bash
gog gmail send --to recipient@example.com \ gog gmail send --to recipient@example.com \
@@ -84,6 +107,7 @@ Email Formatting
``` ```
Notes Notes
- Set `GOG_ACCOUNT=you@gmail.com` to avoid repeating `--account`. - Set `GOG_ACCOUNT=you@gmail.com` to avoid repeating `--account`.
- For scripting, prefer `--json` plus `--no-input`. - For scripting, prefer `--json` plus `--no-input`.
- Sheets values can be passed via `--values-json` (recommended) or as inline rows. - Sheets values can be passed via `--values-json` (recommended) or as inline rows.

View File

@@ -2,7 +2,25 @@
name: goplaces name: goplaces
description: Query Google Places API (New) via the goplaces CLI for text search, place details, resolve, and reviews. Use for human-friendly place lookup or JSON output for scripts. description: Query Google Places API (New) via the goplaces CLI for text search, place details, resolve, and reviews. Use for human-friendly place lookup or JSON output for scripts.
homepage: https://github.com/steipete/goplaces homepage: https://github.com/steipete/goplaces
metadata: {"openclaw":{"emoji":"📍","requires":{"bins":["goplaces"],"env":["GOOGLE_PLACES_API_KEY"]},"primaryEnv":"GOOGLE_PLACES_API_KEY","install":[{"id":"brew","kind":"brew","formula":"steipete/tap/goplaces","bins":["goplaces"],"label":"Install goplaces (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "📍",
"requires": { "bins": ["goplaces"], "env": ["GOOGLE_PLACES_API_KEY"] },
"primaryEnv": "GOOGLE_PLACES_API_KEY",
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/goplaces",
"bins": ["goplaces"],
"label": "Install goplaces (brew)",
},
],
},
}
--- ---
# goplaces # goplaces
@@ -10,13 +28,16 @@ metadata: {"openclaw":{"emoji":"📍","requires":{"bins":["goplaces"],"env":["GO
Modern Google Places API (New) CLI. Human output by default, `--json` for scripts. Modern Google Places API (New) CLI. Human output by default, `--json` for scripts.
Install Install
- Homebrew: `brew install steipete/tap/goplaces` - Homebrew: `brew install steipete/tap/goplaces`
Config Config
- `GOOGLE_PLACES_API_KEY` required. - `GOOGLE_PLACES_API_KEY` required.
- Optional: `GOOGLE_PLACES_BASE_URL` for testing/proxying. - Optional: `GOOGLE_PLACES_BASE_URL` for testing/proxying.
Common commands Common commands
- Search: `goplaces search "coffee" --open-now --min-rating 4 --limit 5` - Search: `goplaces search "coffee" --open-now --min-rating 4 --limit 5`
- Bias: `goplaces search "pizza" --lat 40.8 --lng -73.9 --radius-m 3000` - Bias: `goplaces search "pizza" --lat 40.8 --lng -73.9 --radius-m 3000`
- Pagination: `goplaces search "pizza" --page-token "NEXT_PAGE_TOKEN"` - Pagination: `goplaces search "pizza" --page-token "NEXT_PAGE_TOKEN"`
@@ -25,6 +46,7 @@ Common commands
- JSON: `goplaces search "sushi" --json` - JSON: `goplaces search "sushi" --json`
Notes Notes
- `--no-color` or `NO_COLOR` disables ANSI color. - `--no-color` or `NO_COLOR` disables ANSI color.
- Price levels: 0..4 (free → very expensive). - Price levels: 0..4 (free → very expensive).
- Type filter sends only the first `--type` value (API accepts one). - Type filter sends only the first `--type` value (API accepts one).

View File

@@ -2,7 +2,24 @@
name: himalaya name: himalaya
description: "CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language)." description: "CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language)."
homepage: https://github.com/pimalaya/himalaya homepage: https://github.com/pimalaya/himalaya
metadata: {"openclaw":{"emoji":"📧","requires":{"bins":["himalaya"]},"install":[{"id":"brew","kind":"brew","formula":"himalaya","bins":["himalaya"],"label":"Install Himalaya (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "📧",
"requires": { "bins": ["himalaya"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "himalaya",
"bins": ["himalaya"],
"label": "Install Himalaya (brew)",
},
],
},
}
--- ---
# Himalaya Email CLI # Himalaya Email CLI
@@ -23,11 +40,13 @@ Himalaya is a CLI email client that lets you manage emails from the terminal usi
## Configuration Setup ## Configuration Setup
Run the interactive wizard to set up an account: Run the interactive wizard to set up an account:
```bash ```bash
himalaya account configure himalaya account configure
``` ```
Or create `~/.config/himalaya/config.toml` manually: Or create `~/.config/himalaya/config.toml` manually:
```toml ```toml
[accounts.personal] [accounts.personal]
email = "you@example.com" email = "you@example.com"
@@ -62,16 +81,19 @@ himalaya folder list
### List Emails ### List Emails
List emails in INBOX (default): List emails in INBOX (default):
```bash ```bash
himalaya envelope list himalaya envelope list
``` ```
List emails in a specific folder: List emails in a specific folder:
```bash ```bash
himalaya envelope list --folder "Sent" himalaya envelope list --folder "Sent"
``` ```
List with pagination: List with pagination:
```bash ```bash
himalaya envelope list --page 1 --page-size 20 himalaya envelope list --page 1 --page-size 20
``` ```
@@ -85,11 +107,13 @@ himalaya envelope list from john@example.com subject meeting
### Read an Email ### Read an Email
Read email by ID (shows plain text): Read email by ID (shows plain text):
```bash ```bash
himalaya message read 42 himalaya message read 42
``` ```
Export raw MIME: Export raw MIME:
```bash ```bash
himalaya message export 42 --full himalaya message export 42 --full
``` ```
@@ -97,11 +121,13 @@ himalaya message export 42 --full
### Reply to an Email ### Reply to an Email
Interactive reply (opens $EDITOR): Interactive reply (opens $EDITOR):
```bash ```bash
himalaya message reply 42 himalaya message reply 42
``` ```
Reply-all: Reply-all:
```bash ```bash
himalaya message reply 42 --all himalaya message reply 42 --all
``` ```
@@ -115,11 +141,13 @@ himalaya message forward 42
### Write a New Email ### Write a New Email
Interactive compose (opens $EDITOR): Interactive compose (opens $EDITOR):
```bash ```bash
himalaya message write himalaya message write
``` ```
Send directly using template: Send directly using template:
```bash ```bash
cat << 'EOF' | himalaya template send cat << 'EOF' | himalaya template send
From: you@example.com From: you@example.com
@@ -131,6 +159,7 @@ EOF
``` ```
Or with headers flag: Or with headers flag:
```bash ```bash
himalaya message write -H "To:recipient@example.com" -H "Subject:Test" "Message body here" himalaya message write -H "To:recipient@example.com" -H "Subject:Test" "Message body here"
``` ```
@@ -138,11 +167,13 @@ himalaya message write -H "To:recipient@example.com" -H "Subject:Test" "Message
### Move/Copy Emails ### Move/Copy Emails
Move to folder: Move to folder:
```bash ```bash
himalaya message move 42 "Archive" himalaya message move 42 "Archive"
``` ```
Copy to folder: Copy to folder:
```bash ```bash
himalaya message copy 42 "Important" himalaya message copy 42 "Important"
``` ```
@@ -156,11 +187,13 @@ himalaya message delete 42
### Manage Flags ### Manage Flags
Add flag: Add flag:
```bash ```bash
himalaya flag add 42 --flag seen himalaya flag add 42 --flag seen
``` ```
Remove flag: Remove flag:
```bash ```bash
himalaya flag remove 42 --flag seen himalaya flag remove 42 --flag seen
``` ```
@@ -168,11 +201,13 @@ himalaya flag remove 42 --flag seen
## Multiple Accounts ## Multiple Accounts
List accounts: List accounts:
```bash ```bash
himalaya account list himalaya account list
``` ```
Use a specific account: Use a specific account:
```bash ```bash
himalaya --account work envelope list himalaya --account work envelope list
``` ```
@@ -180,11 +215,13 @@ himalaya --account work envelope list
## Attachments ## Attachments
Save attachments from a message: Save attachments from a message:
```bash ```bash
himalaya attachment download 42 himalaya attachment download 42
``` ```
Save to specific directory: Save to specific directory:
```bash ```bash
himalaya attachment download 42 --dir ~/Downloads himalaya attachment download 42 --dir ~/Downloads
``` ```
@@ -192,6 +229,7 @@ himalaya attachment download 42 --dir ~/Downloads
## Output Formats ## Output Formats
Most commands support `--output` for structured output: Most commands support `--output` for structured output:
```bash ```bash
himalaya envelope list --output json himalaya envelope list --output json
himalaya envelope list --output plain himalaya envelope list --output plain
@@ -200,11 +238,13 @@ himalaya envelope list --output plain
## Debugging ## Debugging
Enable debug logging: Enable debug logging:
```bash ```bash
RUST_LOG=debug himalaya envelope list RUST_LOG=debug himalaya envelope list
``` ```
Full trace with backtrace: Full trace with backtrace:
```bash ```bash
RUST_LOG=trace RUST_BACKTRACE=1 himalaya envelope list RUST_LOG=trace RUST_BACKTRACE=1 himalaya envelope list
``` ```

View File

@@ -32,20 +32,24 @@ message.send.backend.auth.raw = "your-password"
## Password Options ## Password Options
### Raw password (testing only, not recommended) ### Raw password (testing only, not recommended)
```toml ```toml
backend.auth.raw = "your-password" backend.auth.raw = "your-password"
``` ```
### Password from command (recommended) ### Password from command (recommended)
```toml ```toml
backend.auth.cmd = "pass show email/imap" backend.auth.cmd = "pass show email/imap"
# backend.auth.cmd = "security find-generic-password -a user@example.com -s imap -w" # backend.auth.cmd = "security find-generic-password -a user@example.com -s imap -w"
``` ```
### System keyring (requires keyring feature) ### System keyring (requires keyring feature)
```toml ```toml
backend.auth.keyring = "imap-example" backend.auth.keyring = "imap-example"
``` ```
Then run `himalaya account configure <account>` to store the password. Then run `himalaya account configure <account>` to store the password.
## Gmail Configuration ## Gmail Configuration
@@ -104,6 +108,7 @@ message.send.backend.auth.cmd = "pass show icloud/app-password"
## Folder Aliases ## Folder Aliases
Map custom folder names: Map custom folder names:
```toml ```toml
[accounts.default.folder.alias] [accounts.default.folder.alias]
inbox = "INBOX" inbox = "INBOX"
@@ -126,6 +131,7 @@ email = "work@company.com"
``` ```
Switch accounts with `--account`: Switch accounts with `--account`:
```bash ```bash
himalaya --account work envelope list himalaya --account work envelope list
``` ```
@@ -155,6 +161,7 @@ backend.auth.token-url = "https://provider.com/oauth/token"
## Additional Options ## Additional Options
### Signature ### Signature
```toml ```toml
[accounts.default] [accounts.default]
signature = "Best regards,\nYour Name" signature = "Best regards,\nYour Name"
@@ -162,13 +169,16 @@ signature-delim = "-- \n"
``` ```
### Downloads directory ### Downloads directory
```toml ```toml
[accounts.default] [accounts.default]
downloads-dir = "~/Downloads/himalaya" downloads-dir = "~/Downloads/himalaya"
``` ```
### Editor for composing ### Editor for composing
Set via environment variable: Set via environment variable:
```bash ```bash
export EDITOR="vim" export EDITOR="vim"
``` ```

View File

@@ -17,6 +17,7 @@ This is the message body.
## Headers ## Headers
Common headers: Common headers:
- `From`: Sender address - `From`: Sender address
- `To`: Primary recipient(s) - `To`: Primary recipient(s)
- `Cc`: Carbon copy recipients - `Cc`: Carbon copy recipients
@@ -37,6 +38,7 @@ To: user1@example.com, user2@example.com, "Jane" <jane@example.com>
## Plain Text Body ## Plain Text Body
Simple plain text email: Simple plain text email:
``` ```
From: alice@localhost From: alice@localhost
To: bob@localhost To: bob@localhost
@@ -54,6 +56,7 @@ Alice
### Multipart Messages ### Multipart Messages
Alternative text/html parts: Alternative text/html parts:
``` ```
From: alice@localhost From: alice@localhost
To: bob@localhost To: bob@localhost
@@ -69,6 +72,7 @@ This is the plain text version.
### Attachments ### Attachments
Attach a file: Attach a file:
``` ```
From: alice@localhost From: alice@localhost
To: bob@localhost To: bob@localhost
@@ -80,11 +84,13 @@ Here is the document you requested.
``` ```
Attachment with custom name: Attachment with custom name:
``` ```
<#part filename=/path/to/file.pdf name=report.pdf><#/part> <#part filename=/path/to/file.pdf name=report.pdf><#/part>
``` ```
Multiple attachments: Multiple attachments:
``` ```
<#part filename=/path/to/doc1.pdf><#/part> <#part filename=/path/to/doc1.pdf><#/part>
<#part filename=/path/to/doc2.pdf><#/part> <#part filename=/path/to/doc2.pdf><#/part>
@@ -93,6 +99,7 @@ Multiple attachments:
### Inline Images ### Inline Images
Embed an image inline: Embed an image inline:
``` ```
From: alice@localhost From: alice@localhost
To: bob@localhost To: bob@localhost
@@ -129,13 +136,17 @@ Alice
## MML Tag Reference ## MML Tag Reference
### `<#multipart>` ### `<#multipart>`
Groups multiple parts together. Groups multiple parts together.
- `type=alternative`: Different representations of same content - `type=alternative`: Different representations of same content
- `type=mixed`: Independent parts (text + attachments) - `type=mixed`: Independent parts (text + attachments)
- `type=related`: Parts that reference each other (HTML + images) - `type=related`: Parts that reference each other (HTML + images)
### `<#part>` ### `<#part>`
Defines a message part. Defines a message part.
- `type=<mime-type>`: Content type (e.g., `text/html`, `application/pdf`) - `type=<mime-type>`: Content type (e.g., `text/html`, `application/pdf`)
- `filename=<path>`: File to attach - `filename=<path>`: File to attach
- `name=<name>`: Display name for attachment - `name=<name>`: Display name for attachment
@@ -145,28 +156,34 @@ Defines a message part.
## Composing from CLI ## Composing from CLI
### Interactive compose ### Interactive compose
Opens your `$EDITOR`: Opens your `$EDITOR`:
```bash ```bash
himalaya message write himalaya message write
``` ```
### Reply (opens editor with quoted message) ### Reply (opens editor with quoted message)
```bash ```bash
himalaya message reply 42 himalaya message reply 42
himalaya message reply 42 --all # reply-all himalaya message reply 42 --all # reply-all
``` ```
### Forward ### Forward
```bash ```bash
himalaya message forward 42 himalaya message forward 42
``` ```
### Send from stdin ### Send from stdin
```bash ```bash
cat message.txt | himalaya template send cat message.txt | himalaya template send
``` ```
### Prefill headers from CLI ### Prefill headers from CLI
```bash ```bash
himalaya message write \ himalaya message write \
-H "To:recipient@example.com" \ -H "To:recipient@example.com" \

View File

@@ -2,7 +2,25 @@
name: imsg name: imsg
description: iMessage/SMS CLI for listing chats, history, watch, and sending. description: iMessage/SMS CLI for listing chats, history, watch, and sending.
homepage: https://imsg.to homepage: https://imsg.to
metadata: {"openclaw":{"emoji":"📨","os":["darwin"],"requires":{"bins":["imsg"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/imsg","bins":["imsg"],"label":"Install imsg (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "📨",
"os": ["darwin"],
"requires": { "bins": ["imsg"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/imsg",
"bins": ["imsg"],
"label": "Install imsg (brew)",
},
],
},
}
--- ---
# imsg # imsg
@@ -10,16 +28,19 @@ metadata: {"openclaw":{"emoji":"📨","os":["darwin"],"requires":{"bins":["imsg"
Use `imsg` to read and send Messages.app iMessage/SMS on macOS. Use `imsg` to read and send Messages.app iMessage/SMS on macOS.
Requirements Requirements
- Messages.app signed in - Messages.app signed in
- Full Disk Access for your terminal - Full Disk Access for your terminal
- Automation permission to control Messages.app (for sending) - Automation permission to control Messages.app (for sending)
Common commands Common commands
- List chats: `imsg chats --limit 10 --json` - List chats: `imsg chats --limit 10 --json`
- History: `imsg history --chat-id 1 --limit 20 --attachments --json` - History: `imsg history --chat-id 1 --limit 20 --attachments --json`
- Watch: `imsg watch --chat-id 1 --attachments` - Watch: `imsg watch --chat-id 1 --attachments`
- Send: `imsg send --to "+14155551212" --text "hi" --file /path/pic.jpg` - Send: `imsg send --to "+14155551212" --text "hi" --file /path/pic.jpg`
Notes Notes
- `--service imessage|sms|auto` controls delivery. - `--service imessage|sms|auto` controls delivery.
- Confirm recipient + message before sending. - Confirm recipient + message before sending.

View File

@@ -2,12 +2,20 @@
name: local-places name: local-places
description: Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost. description: Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
homepage: https://github.com/Hyaxia/local_places homepage: https://github.com/Hyaxia/local_places
metadata: {"openclaw":{"emoji":"📍","requires":{"bins":["uv"],"env":["GOOGLE_PLACES_API_KEY"]},"primaryEnv":"GOOGLE_PLACES_API_KEY"}} metadata:
{
"openclaw":
{
"emoji": "📍",
"requires": { "bins": ["uv"], "env": ["GOOGLE_PLACES_API_KEY"] },
"primaryEnv": "GOOGLE_PLACES_API_KEY",
},
}
--- ---
# 📍 Local Places # 📍 Local Places
*Find places, Go fast* _Find places, Go fast_
Search for nearby places using a local Google Places API proxy. Two-step flow: resolve location first, then search. Search for nearby places using a local Google Places API proxy. Two-step flow: resolve location first, then search.
@@ -27,6 +35,7 @@ Requires `GOOGLE_PLACES_API_KEY` in `.env` or environment.
1. **Check server:** `curl http://127.0.0.1:8000/ping` 1. **Check server:** `curl http://127.0.0.1:8000/ping`
2. **Resolve location:** 2. **Resolve location:**
```bash ```bash
curl -X POST http://127.0.0.1:8000/locations/resolve \ curl -X POST http://127.0.0.1:8000/locations/resolve \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
@@ -34,6 +43,7 @@ curl -X POST http://127.0.0.1:8000/locations/resolve \
``` ```
3. **Search places:** 3. **Search places:**
```bash ```bash
curl -X POST http://127.0.0.1:8000/places/search \ curl -X POST http://127.0.0.1:8000/places/search \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
@@ -46,6 +56,7 @@ curl -X POST http://127.0.0.1:8000/places/search \
``` ```
4. **Get details:** 4. **Get details:**
```bash ```bash
curl http://127.0.0.1:8000/places/{place_id} curl http://127.0.0.1:8000/places/{place_id}
``` ```
@@ -77,7 +88,7 @@ curl http://127.0.0.1:8000/places/{place_id}
"place_id": "ChIJ...", "place_id": "ChIJ...",
"name": "Coffee Shop", "name": "Coffee Shop",
"address": "123 Main St", "address": "123 Main St",
"location": {"lat": 51.5, "lng": -0.1}, "location": { "lat": 51.5, "lng": -0.1 },
"rating": 4.6, "rating": 4.6,
"price_level": 2, "price_level": 2,
"types": ["cafe", "food"], "types": ["cafe", "food"],

View File

@@ -4,16 +4,10 @@ version = "0.1.0"
description = "FastAPI server" description = "FastAPI server"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = ["fastapi>=0.110.0", "httpx>=0.27.0", "uvicorn[standard]>=0.29.0"]
"fastapi>=0.110.0",
"httpx>=0.27.0",
"uvicorn[standard]>=0.29.0",
]
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = ["pytest>=8.0.0"]
"pytest>=8.0.0",
]
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]

View File

@@ -2,7 +2,24 @@
name: mcporter name: mcporter
description: Use the mcporter CLI to list, configure, auth, and call MCP servers/tools directly (HTTP or stdio), including ad-hoc servers, config edits, and CLI/type generation. description: Use the mcporter CLI to list, configure, auth, and call MCP servers/tools directly (HTTP or stdio), including ad-hoc servers, config edits, and CLI/type generation.
homepage: http://mcporter.dev homepage: http://mcporter.dev
metadata: {"openclaw":{"emoji":"📦","requires":{"bins":["mcporter"]},"install":[{"id":"node","kind":"node","package":"mcporter","bins":["mcporter"],"label":"Install mcporter (node)"}]}} metadata:
{
"openclaw":
{
"emoji": "📦",
"requires": { "bins": ["mcporter"] },
"install":
[
{
"id": "node",
"kind": "node",
"package": "mcporter",
"bins": ["mcporter"],
"label": "Install mcporter (node)",
},
],
},
}
--- ---
# mcporter # mcporter
@@ -10,11 +27,13 @@ metadata: {"openclaw":{"emoji":"📦","requires":{"bins":["mcporter"]},"install"
Use `mcporter` to work with MCP servers directly. Use `mcporter` to work with MCP servers directly.
Quick start Quick start
- `mcporter list` - `mcporter list`
- `mcporter list <server> --schema` - `mcporter list <server> --schema`
- `mcporter call <server.tool> key=value` - `mcporter call <server.tool> key=value`
Call tools Call tools
- Selector: `mcporter call linear.list_issues team=ENG limit:5` - Selector: `mcporter call linear.list_issues team=ENG limit:5`
- Function syntax: `mcporter call "linear.create_issue(title: \"Bug\")"` - Function syntax: `mcporter call "linear.create_issue(title: \"Bug\")"`
- Full URL: `mcporter call https://api.example.com/mcp.fetch url:https://example.com` - Full URL: `mcporter call https://api.example.com/mcp.fetch url:https://example.com`
@@ -22,17 +41,21 @@ Call tools
- JSON payload: `mcporter call <server.tool> --args '{"limit":5}'` - JSON payload: `mcporter call <server.tool> --args '{"limit":5}'`
Auth + config Auth + config
- OAuth: `mcporter auth <server | url> [--reset]` - OAuth: `mcporter auth <server | url> [--reset]`
- Config: `mcporter config list|get|add|remove|import|login|logout` - Config: `mcporter config list|get|add|remove|import|login|logout`
Daemon Daemon
- `mcporter daemon start|status|stop|restart` - `mcporter daemon start|status|stop|restart`
Codegen Codegen
- CLI: `mcporter generate-cli --server <name>` or `--command <url>` - CLI: `mcporter generate-cli --server <name>` or `--command <url>`
- Inspect: `mcporter inspect-cli <path> [--json]` - Inspect: `mcporter inspect-cli <path> [--json]`
- TS: `mcporter emit-ts <server> --mode client|types` - TS: `mcporter emit-ts <server> --mode client|types`
Notes Notes
- Config default: `./config/mcporter.json` (override with `--config`). - Config default: `./config/mcporter.json` (override with `--config`).
- Prefer `--output json` for machine-readable results. - Prefer `--output json` for machine-readable results.

View File

@@ -1,19 +1,39 @@
--- ---
name: model-usage name: model-usage
description: Use CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON. description: Use CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
metadata: {"openclaw":{"emoji":"📊","os":["darwin"],"requires":{"bins":["codexbar"]},"install":[{"id":"brew-cask","kind":"brew","cask":"steipete/tap/codexbar","bins":["codexbar"],"label":"Install CodexBar (brew cask)"}]}} metadata:
{
"openclaw":
{
"emoji": "📊",
"os": ["darwin"],
"requires": { "bins": ["codexbar"] },
"install":
[
{
"id": "brew-cask",
"kind": "brew",
"cask": "steipete/tap/codexbar",
"bins": ["codexbar"],
"label": "Install CodexBar (brew cask)",
},
],
},
}
--- ---
# Model usage # Model usage
## Overview ## Overview
Get per-model usage cost from CodexBar's local cost logs. Supports "current model" (most recent daily entry) or "all models" summaries for Codex or Claude. Get per-model usage cost from CodexBar's local cost logs. Supports "current model" (most recent daily entry) or "all models" summaries for Codex or Claude.
TODO: add Linux CLI support guidance once CodexBar CLI install path is documented for Linux. TODO: add Linux CLI support guidance once CodexBar CLI install path is documented for Linux.
## Quick start ## Quick start
1) Fetch cost JSON via CodexBar CLI or pass a JSON file.
2) Use the bundled script to summarize by model. 1. Fetch cost JSON via CodexBar CLI or pass a JSON file.
2. Use the bundled script to summarize by model.
```bash ```bash
python {baseDir}/scripts/model_usage.py --provider codex --mode current python {baseDir}/scripts/model_usage.py --provider codex --mode current
@@ -22,12 +42,14 @@ python {baseDir}/scripts/model_usage.py --provider claude --mode all --format js
``` ```
## Current model logic ## Current model logic
- Uses the most recent daily row with `modelBreakdowns`. - Uses the most recent daily row with `modelBreakdowns`.
- Picks the model with the highest cost in that row. - Picks the model with the highest cost in that row.
- Falls back to the last entry in `modelsUsed` when breakdowns are missing. - Falls back to the last entry in `modelsUsed` when breakdowns are missing.
- Override with `--model <name>` when you need a specific model. - Override with `--model <name>` when you need a specific model.
## Inputs ## Inputs
- Default: runs `codexbar cost --format json --provider <codex|claude>`. - Default: runs `codexbar cost --format json --provider <codex|claude>`.
- File or stdin: - File or stdin:
@@ -38,8 +60,10 @@ cat /tmp/cost.json | python {baseDir}/scripts/model_usage.py --input - --mode cu
``` ```
## Output ## Output
- Text (default) or JSON (`--format json --pretty`). - Text (default) or JSON (`--format json --pretty`).
- Values are cost-only per model; tokens are not split by model in CodexBar output. - Values are cost-only per model; tokens are not split by model in CodexBar output.
## References ## References
- Read `references/codexbar-cli.md` for CLI flags and cost JSON fields. - Read `references/codexbar-cli.md` for CLI flags and cost JSON fields.

View File

@@ -1,10 +1,12 @@
# CodexBar CLI quick ref (usage + cost) # CodexBar CLI quick ref (usage + cost)
## Install ## Install
- App: Preferences -> Advanced -> Install CLI - App: Preferences -> Advanced -> Install CLI
- Repo: ./bin/install-codexbar-cli.sh - Repo: ./bin/install-codexbar-cli.sh
## Commands ## Commands
- Usage snapshot (web/cli sources): - Usage snapshot (web/cli sources):
- codexbar usage --format json --pretty - codexbar usage --format json --pretty
- codexbar --provider all --format json - codexbar --provider all --format json
@@ -13,7 +15,9 @@
- codexbar cost --provider codex|claude --format json - codexbar cost --provider codex|claude --format json
## Cost JSON fields ## Cost JSON fields
The payload is an array (one per provider). The payload is an array (one per provider).
- provider, source, updatedAt - provider, source, updatedAt
- sessionTokens, sessionCostUSD - sessionTokens, sessionCostUSD
- last30DaysTokens, last30DaysCostUSD - last30DaysTokens, last30DaysCostUSD
@@ -22,7 +26,8 @@ The payload is an array (one per provider).
- totals: totalInputTokens, totalOutputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, totalCost - totals: totalInputTokens, totalOutputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, totalCost
## Notes ## Notes
- Cost usage is local-only. It reads JSONL logs under: - Cost usage is local-only. It reads JSONL logs under:
- Codex: ~/.codex/sessions/**/*.jsonl - Codex: ~/.codex/sessions/\*_/_.jsonl
- Claude: ~/.config/claude/projects/**/*.jsonl or ~/.claude/projects/**/*.jsonl - Claude: ~/.config/claude/projects/**/\*.jsonl or ~/.claude/projects/**/\*.jsonl
- If web usage is required (non-local), use codexbar usage (not cost). - If web usage is required (non-local), use codexbar usage (not cost).

View File

@@ -2,7 +2,25 @@
name: nano-banana-pro name: nano-banana-pro
description: Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro). description: Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro).
homepage: https://ai.google.dev/ homepage: https://ai.google.dev/
metadata: {"openclaw":{"emoji":"🍌","requires":{"bins":["uv"],"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY","install":[{"id":"uv-brew","kind":"brew","formula":"uv","bins":["uv"],"label":"Install uv (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🍌",
"requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"] },
"primaryEnv": "GEMINI_API_KEY",
"install":
[
{
"id": "uv-brew",
"kind": "brew",
"formula": "uv",
"bins": ["uv"],
"label": "Install uv (brew)",
},
],
},
}
--- ---
# Nano Banana Pro (Gemini 3 Pro Image) # Nano Banana Pro (Gemini 3 Pro Image)
@@ -10,25 +28,30 @@ metadata: {"openclaw":{"emoji":"🍌","requires":{"bins":["uv"],"env":["GEMINI_A
Use the bundled script to generate or edit images. Use the bundled script to generate or edit images.
Generate Generate
```bash ```bash
uv run {baseDir}/scripts/generate_image.py --prompt "your image description" --filename "output.png" --resolution 1K uv run {baseDir}/scripts/generate_image.py --prompt "your image description" --filename "output.png" --resolution 1K
``` ```
Edit (single image) Edit (single image)
```bash ```bash
uv run {baseDir}/scripts/generate_image.py --prompt "edit instructions" --filename "output.png" -i "/path/in.png" --resolution 2K uv run {baseDir}/scripts/generate_image.py --prompt "edit instructions" --filename "output.png" -i "/path/in.png" --resolution 2K
``` ```
Multi-image composition (up to 14 images) Multi-image composition (up to 14 images)
```bash ```bash
uv run {baseDir}/scripts/generate_image.py --prompt "combine these into one scene" --filename "output.png" -i img1.png -i img2.png -i img3.png uv run {baseDir}/scripts/generate_image.py --prompt "combine these into one scene" --filename "output.png" -i img1.png -i img2.png -i img3.png
``` ```
API key API key
- `GEMINI_API_KEY` env var - `GEMINI_API_KEY` env var
- Or set `skills."nano-banana-pro".apiKey` / `skills."nano-banana-pro".env.GEMINI_API_KEY` in `~/.openclaw/openclaw.json` - Or set `skills."nano-banana-pro".apiKey` / `skills."nano-banana-pro".env.GEMINI_API_KEY` in `~/.openclaw/openclaw.json`
Notes Notes
- Resolutions: `1K` (default), `2K`, `4K`. - Resolutions: `1K` (default), `2K`, `4K`.
- Use timestamps in filenames: `yyyy-mm-dd-hh-mm-ss-name.png`. - Use timestamps in filenames: `yyyy-mm-dd-hh-mm-ss-name.png`.
- The script prints a `MEDIA:` line for OpenClaw to auto-attach on supported chat providers. - The script prints a `MEDIA:` line for OpenClaw to auto-attach on supported chat providers.

View File

@@ -2,7 +2,24 @@
name: nano-pdf name: nano-pdf
description: Edit PDFs with natural-language instructions using the nano-pdf CLI. description: Edit PDFs with natural-language instructions using the nano-pdf CLI.
homepage: https://pypi.org/project/nano-pdf/ homepage: https://pypi.org/project/nano-pdf/
metadata: {"openclaw":{"emoji":"📄","requires":{"bins":["nano-pdf"]},"install":[{"id":"uv","kind":"uv","package":"nano-pdf","bins":["nano-pdf"],"label":"Install nano-pdf (uv)"}]}} metadata:
{
"openclaw":
{
"emoji": "📄",
"requires": { "bins": ["nano-pdf"] },
"install":
[
{
"id": "uv",
"kind": "uv",
"package": "nano-pdf",
"bins": ["nano-pdf"],
"label": "Install nano-pdf (uv)",
},
],
},
}
--- ---
# nano-pdf # nano-pdf
@@ -16,5 +33,6 @@ nano-pdf edit deck.pdf 1 "Change the title to 'Q3 Results' and fix the typo in t
``` ```
Notes: Notes:
- Page numbers are 0-based or 1-based depending on the tools version/config; if the result looks off by one, retry with the other. - Page numbers are 0-based or 1-based depending on the tools version/config; if the result looks off by one, retry with the other.
- Always sanity-check the output PDF before sending it out. - Always sanity-check the output PDF before sending it out.

View File

@@ -2,7 +2,11 @@
name: notion name: notion
description: Notion API for creating and managing pages, databases, and blocks. description: Notion API for creating and managing pages, databases, and blocks.
homepage: https://developers.notion.com homepage: https://developers.notion.com
metadata: {"openclaw":{"emoji":"📝","requires":{"env":["NOTION_API_KEY"]},"primaryEnv":"NOTION_API_KEY"}} metadata:
{
"openclaw":
{ "emoji": "📝", "requires": { "env": ["NOTION_API_KEY"] }, "primaryEnv": "NOTION_API_KEY" },
}
--- ---
# notion # notion
@@ -14,15 +18,18 @@ Use the Notion API to create/read/update pages, data sources (databases), and bl
1. Create an integration at https://notion.so/my-integrations 1. Create an integration at https://notion.so/my-integrations
2. Copy the API key (starts with `ntn_` or `secret_`) 2. Copy the API key (starts with `ntn_` or `secret_`)
3. Store it: 3. Store it:
```bash ```bash
mkdir -p ~/.config/notion mkdir -p ~/.config/notion
echo "ntn_your_key_here" > ~/.config/notion/api_key echo "ntn_your_key_here" > ~/.config/notion/api_key
``` ```
4. Share target pages/databases with your integration (click "..." → "Connect to" → your integration name) 4. Share target pages/databases with your integration (click "..." → "Connect to" → your integration name)
## API Basics ## API Basics
All requests need: All requests need:
```bash ```bash
NOTION_KEY=$(cat ~/.config/notion/api_key) NOTION_KEY=$(cat ~/.config/notion/api_key)
curl -X GET "https://api.notion.com/v1/..." \ curl -X GET "https://api.notion.com/v1/..." \
@@ -36,6 +43,7 @@ curl -X GET "https://api.notion.com/v1/..." \
## Common Operations ## Common Operations
**Search for pages and data sources:** **Search for pages and data sources:**
```bash ```bash
curl -X POST "https://api.notion.com/v1/search" \ curl -X POST "https://api.notion.com/v1/search" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -45,6 +53,7 @@ curl -X POST "https://api.notion.com/v1/search" \
``` ```
**Get page:** **Get page:**
```bash ```bash
curl "https://api.notion.com/v1/pages/{page_id}" \ curl "https://api.notion.com/v1/pages/{page_id}" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -52,6 +61,7 @@ curl "https://api.notion.com/v1/pages/{page_id}" \
``` ```
**Get page content (blocks):** **Get page content (blocks):**
```bash ```bash
curl "https://api.notion.com/v1/blocks/{page_id}/children" \ curl "https://api.notion.com/v1/blocks/{page_id}/children" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -59,6 +69,7 @@ curl "https://api.notion.com/v1/blocks/{page_id}/children" \
``` ```
**Create page in a data source:** **Create page in a data source:**
```bash ```bash
curl -X POST "https://api.notion.com/v1/pages" \ curl -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -74,6 +85,7 @@ curl -X POST "https://api.notion.com/v1/pages" \
``` ```
**Query a data source (database):** **Query a data source (database):**
```bash ```bash
curl -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query" \ curl -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -86,6 +98,7 @@ curl -X POST "https://api.notion.com/v1/data_sources/{data_source_id}/query" \
``` ```
**Create a data source (database):** **Create a data source (database):**
```bash ```bash
curl -X POST "https://api.notion.com/v1/data_sources" \ curl -X POST "https://api.notion.com/v1/data_sources" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -103,6 +116,7 @@ curl -X POST "https://api.notion.com/v1/data_sources" \
``` ```
**Update page properties:** **Update page properties:**
```bash ```bash
curl -X PATCH "https://api.notion.com/v1/pages/{page_id}" \ curl -X PATCH "https://api.notion.com/v1/pages/{page_id}" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -112,6 +126,7 @@ curl -X PATCH "https://api.notion.com/v1/pages/{page_id}" \
``` ```
**Add blocks to page:** **Add blocks to page:**
```bash ```bash
curl -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \ curl -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \
-H "Authorization: Bearer $NOTION_KEY" \ -H "Authorization: Bearer $NOTION_KEY" \
@@ -127,6 +142,7 @@ curl -X PATCH "https://api.notion.com/v1/blocks/{page_id}/children" \
## Property Types ## Property Types
Common property formats for database items: Common property formats for database items:
- **Title:** `{"title": [{"text": {"content": "..."}}]}` - **Title:** `{"title": [{"text": {"content": "..."}}]}`
- **Rich text:** `{"rich_text": [{"text": {"content": "..."}}]}` - **Rich text:** `{"rich_text": [{"text": {"content": "..."}}]}`
- **Select:** `{"select": {"name": "Option"}}` - **Select:** `{"select": {"name": "Option"}}`

View File

@@ -2,7 +2,24 @@
name: obsidian name: obsidian
description: Work with Obsidian vaults (plain Markdown notes) and automate via obsidian-cli. description: Work with Obsidian vaults (plain Markdown notes) and automate via obsidian-cli.
homepage: https://help.obsidian.md homepage: https://help.obsidian.md
metadata: {"openclaw":{"emoji":"💎","requires":{"bins":["obsidian-cli"]},"install":[{"id":"brew","kind":"brew","formula":"yakitrak/yakitrak/obsidian-cli","bins":["obsidian-cli"],"label":"Install obsidian-cli (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "💎",
"requires": { "bins": ["obsidian-cli"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "yakitrak/yakitrak/obsidian-cli",
"bins": ["obsidian-cli"],
"label": "Install obsidian-cli (brew)",
},
],
},
}
--- ---
# Obsidian # Obsidian
@@ -10,6 +27,7 @@ metadata: {"openclaw":{"emoji":"💎","requires":{"bins":["obsidian-cli"]},"inst
Obsidian vault = a normal folder on disk. Obsidian vault = a normal folder on disk.
Vault structure (typical) Vault structure (typical)
- Notes: `*.md` (plain text Markdown; edit with any editor) - Notes: `*.md` (plain text Markdown; edit with any editor)
- Config: `.obsidian/` (workspace + plugin settings; usually dont touch from scripts) - Config: `.obsidian/` (workspace + plugin settings; usually dont touch from scripts)
- Canvases: `*.canvas` (JSON) - Canvases: `*.canvas` (JSON)
@@ -18,38 +36,46 @@ Vault structure (typical)
## Find the active vault(s) ## Find the active vault(s)
Obsidian desktop tracks vaults here (source of truth): Obsidian desktop tracks vaults here (source of truth):
- `~/Library/Application Support/obsidian/obsidian.json` - `~/Library/Application Support/obsidian/obsidian.json`
`obsidian-cli` resolves vaults from that file; vault name is typically the **folder name** (path suffix). `obsidian-cli` resolves vaults from that file; vault name is typically the **folder name** (path suffix).
Fast “what vault is active / where are the notes?” Fast “what vault is active / where are the notes?”
- If youve already set a default: `obsidian-cli print-default --path-only` - If youve already set a default: `obsidian-cli print-default --path-only`
- Otherwise, read `~/Library/Application Support/obsidian/obsidian.json` and use the vault entry with `"open": true`. - Otherwise, read `~/Library/Application Support/obsidian/obsidian.json` and use the vault entry with `"open": true`.
Notes Notes
- Multiple vaults common (iCloud vs `~/Documents`, work/personal, etc.). Dont guess; read config. - Multiple vaults common (iCloud vs `~/Documents`, work/personal, etc.). Dont guess; read config.
- Avoid writing hardcoded vault paths into scripts; prefer reading the config or using `print-default`. - Avoid writing hardcoded vault paths into scripts; prefer reading the config or using `print-default`.
## obsidian-cli quick start ## obsidian-cli quick start
Pick a default vault (once): Pick a default vault (once):
- `obsidian-cli set-default "<vault-folder-name>"` - `obsidian-cli set-default "<vault-folder-name>"`
- `obsidian-cli print-default` / `obsidian-cli print-default --path-only` - `obsidian-cli print-default` / `obsidian-cli print-default --path-only`
Search Search
- `obsidian-cli search "query"` (note names) - `obsidian-cli search "query"` (note names)
- `obsidian-cli search-content "query"` (inside notes; shows snippets + lines) - `obsidian-cli search-content "query"` (inside notes; shows snippets + lines)
Create Create
- `obsidian-cli create "Folder/New note" --content "..." --open` - `obsidian-cli create "Folder/New note" --content "..." --open`
- Requires Obsidian URI handler (`obsidian://…`) working (Obsidian installed). - Requires Obsidian URI handler (`obsidian://…`) working (Obsidian installed).
- Avoid creating notes under “hidden” dot-folders (e.g. `.something/...`) via URI; Obsidian may refuse. - Avoid creating notes under “hidden” dot-folders (e.g. `.something/...`) via URI; Obsidian may refuse.
Move/rename (safe refactor) Move/rename (safe refactor)
- `obsidian-cli move "old/path/note" "new/path/note"` - `obsidian-cli move "old/path/note" "new/path/note"`
- Updates `[[wikilinks]]` and common Markdown links across the vault (this is the main win vs `mv`). - Updates `[[wikilinks]]` and common Markdown links across the vault (this is the main win vs `mv`).
Delete Delete
- `obsidian-cli delete "path/note"` - `obsidian-cli delete "path/note"`
Prefer direct edits when appropriate: open the `.md` file and change it; Obsidian will pick it up. Prefer direct edits when appropriate: open the `.md` file and change it; Obsidian will pick it up.

View File

@@ -2,7 +2,25 @@
name: openai-image-gen name: openai-image-gen
description: Batch-generate images via OpenAI Images API. Random prompt sampler + `index.html` gallery. description: Batch-generate images via OpenAI Images API. Random prompt sampler + `index.html` gallery.
homepage: https://platform.openai.com/docs/api-reference/images homepage: https://platform.openai.com/docs/api-reference/images
metadata: {"openclaw":{"emoji":"🖼️","requires":{"bins":["python3"],"env":["OPENAI_API_KEY"]},"primaryEnv":"OPENAI_API_KEY","install":[{"id":"python-brew","kind":"brew","formula":"python","bins":["python3"],"label":"Install Python (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🖼️",
"requires": { "bins": ["python3"], "env": ["OPENAI_API_KEY"] },
"primaryEnv": "OPENAI_API_KEY",
"install":
[
{
"id": "python-brew",
"kind": "brew",
"formula": "python",
"bins": ["python3"],
"label": "Install Python (brew)",
},
],
},
}
--- ---
# OpenAI Image Gen # OpenAI Image Gen

View File

@@ -2,7 +2,15 @@
name: openai-whisper-api name: openai-whisper-api
description: Transcribe audio via OpenAI Audio Transcriptions API (Whisper). description: Transcribe audio via OpenAI Audio Transcriptions API (Whisper).
homepage: https://platform.openai.com/docs/guides/speech-to-text homepage: https://platform.openai.com/docs/guides/speech-to-text
metadata: {"openclaw":{"emoji":"☁️","requires":{"bins":["curl"],"env":["OPENAI_API_KEY"]},"primaryEnv":"OPENAI_API_KEY"}} metadata:
{
"openclaw":
{
"emoji": "☁️",
"requires": { "bins": ["curl"], "env": ["OPENAI_API_KEY"] },
"primaryEnv": "OPENAI_API_KEY",
},
}
--- ---
# OpenAI Whisper API (curl) # OpenAI Whisper API (curl)
@@ -16,6 +24,7 @@ Transcribe an audio file via OpenAIs `/v1/audio/transcriptions` endpoint.
``` ```
Defaults: Defaults:
- Model: `whisper-1` - Model: `whisper-1`
- Output: `<input>.txt` - Output: `<input>.txt`
@@ -36,8 +45,8 @@ Set `OPENAI_API_KEY`, or configure it in `~/.openclaw/openclaw.json`:
{ {
skills: { skills: {
"openai-whisper-api": { "openai-whisper-api": {
apiKey: "OPENAI_KEY_HERE" apiKey: "OPENAI_KEY_HERE",
} },
} },
} }
``` ```

View File

@@ -2,7 +2,24 @@
name: openai-whisper name: openai-whisper
description: Local speech-to-text with the Whisper CLI (no API key). description: Local speech-to-text with the Whisper CLI (no API key).
homepage: https://openai.com/research/whisper homepage: https://openai.com/research/whisper
metadata: {"openclaw":{"emoji":"🎙️","requires":{"bins":["whisper"]},"install":[{"id":"brew","kind":"brew","formula":"openai-whisper","bins":["whisper"],"label":"Install OpenAI Whisper (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🎙️",
"requires": { "bins": ["whisper"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "openai-whisper",
"bins": ["whisper"],
"label": "Install OpenAI Whisper (brew)",
},
],
},
}
--- ---
# Whisper (CLI) # Whisper (CLI)
@@ -10,10 +27,12 @@ metadata: {"openclaw":{"emoji":"🎙️","requires":{"bins":["whisper"]},"instal
Use `whisper` to transcribe audio locally. Use `whisper` to transcribe audio locally.
Quick start Quick start
- `whisper /path/audio.mp3 --model medium --output_format txt --output_dir .` - `whisper /path/audio.mp3 --model medium --output_format txt --output_dir .`
- `whisper /path/audio.m4a --task translate --output_format srt` - `whisper /path/audio.m4a --task translate --output_format srt`
Notes Notes
- Models download to `~/.cache/whisper` on first run. - Models download to `~/.cache/whisper` on first run.
- `--model` defaults to `turbo` on this install. - `--model` defaults to `turbo` on this install.
- Use smaller models for speed, larger for accuracy. - Use smaller models for speed, larger for accuracy.

View File

@@ -2,7 +2,24 @@
name: openhue name: openhue
description: Control Philips Hue lights/scenes via the OpenHue CLI. description: Control Philips Hue lights/scenes via the OpenHue CLI.
homepage: https://www.openhue.io/cli homepage: https://www.openhue.io/cli
metadata: {"openclaw":{"emoji":"💡","requires":{"bins":["openhue"]},"install":[{"id":"brew","kind":"brew","formula":"openhue/cli/openhue-cli","bins":["openhue"],"label":"Install OpenHue CLI (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "💡",
"requires": { "bins": ["openhue"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "openhue/cli/openhue-cli",
"bins": ["openhue"],
"label": "Install OpenHue CLI (brew)",
},
],
},
}
--- ---
# OpenHue CLI # OpenHue CLI
@@ -10,15 +27,18 @@ metadata: {"openclaw":{"emoji":"💡","requires":{"bins":["openhue"]},"install":
Use `openhue` to control Hue lights and scenes via a Hue Bridge. Use `openhue` to control Hue lights and scenes via a Hue Bridge.
Setup Setup
- Discover bridges: `openhue discover` - Discover bridges: `openhue discover`
- Guided setup: `openhue setup` - Guided setup: `openhue setup`
Read Read
- `openhue get light --json` - `openhue get light --json`
- `openhue get room --json` - `openhue get room --json`
- `openhue get scene --json` - `openhue get scene --json`
Write Write
- Turn on: `openhue set light <id-or-name> --on` - Turn on: `openhue set light <id-or-name> --on`
- Turn off: `openhue set light <id-or-name> --off` - Turn off: `openhue set light <id-or-name> --off`
- Brightness: `openhue set light <id> --on --brightness 50` - Brightness: `openhue set light <id> --on --brightness 50`
@@ -26,5 +46,6 @@ Write
- Scene: `openhue set scene <scene-id>` - Scene: `openhue set scene <scene-id>`
Notes Notes
- You may need to press the Hue Bridge button during setup. - You may need to press the Hue Bridge button during setup.
- Use `--room "Room Name"` when light names are ambiguous. - Use `--room "Room Name"` when light names are ambiguous.

View File

@@ -2,7 +2,24 @@
name: oracle name: oracle
description: Best practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns). description: Best practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns).
homepage: https://askoracle.dev homepage: https://askoracle.dev
metadata: {"openclaw":{"emoji":"🧿","requires":{"bins":["oracle"]},"install":[{"id":"node","kind":"node","package":"@steipete/oracle","bins":["oracle"],"label":"Install oracle (node)"}]}} metadata:
{
"openclaw":
{
"emoji": "🧿",
"requires": { "bins": ["oracle"] },
"install":
[
{
"id": "node",
"kind": "node",
"package": "@steipete/oracle",
"bins": ["oracle"],
"label": "Install oracle (node)",
},
],
},
}
--- ---
# oracle — best use # oracle — best use
@@ -14,6 +31,7 @@ Oracle bundles your prompt + selected files into one “one-shot” request so a
Default workflow here: `--engine browser` with GPT5.2 Pro in ChatGPT. This is the common “long think” path: ~10 minutes to ~1 hour is normal; expect a stored session you can reattach to. Default workflow here: `--engine browser` with GPT5.2 Pro in ChatGPT. This is the common “long think” path: ~10 minutes to ~1 hour is normal; expect a stored session you can reattach to.
Recommended defaults: Recommended defaults:
- Engine: browser (`--engine browser`) - Engine: browser (`--engine browser`)
- Model: GPT5.2 Pro (`--model gpt-5.2-pro` or `--model "5.2 Pro"`) - Model: GPT5.2 Pro (`--model gpt-5.2-pro` or `--model "5.2 Pro"`)
@@ -85,6 +103,7 @@ Recommended defaults:
## Prompt template (high signal) ## Prompt template (high signal)
Oracle starts with **zero** project knowledge. Assume the model cannot infer your stack, build tooling, conventions, or “obvious” paths. Include: Oracle starts with **zero** project knowledge. Assume the model cannot infer your stack, build tooling, conventions, or “obvious” paths. Include:
- Project briefing (stack + build/test commands + platform constraints). - Project briefing (stack + build/test commands + platform constraints).
- “Where things live” (key directories, entrypoints, config files, boundaries). - “Where things live” (key directories, entrypoints, config files, boundaries).
- Exact question + what you tried + the error text (verbatim). - Exact question + what you tried + the error text (verbatim).
@@ -98,6 +117,7 @@ Oracle starts with **zero** project knowledge. Assume the model cannot infer you
## “Exhaustive prompt” restoration pattern ## “Exhaustive prompt” restoration pattern
For long investigations, write a standalone prompt + file set so you can rerun days later: For long investigations, write a standalone prompt + file set so you can rerun days later:
- 630 sentence project briefing + the goal. - 630 sentence project briefing + the goal.
- Repro steps + exact errors + what you tried. - Repro steps + exact errors + what you tried.
- Attach all context files needed (entrypoints, configs, key modules, docs). - Attach all context files needed (entrypoints, configs, key modules, docs).

View File

@@ -2,7 +2,31 @@
name: ordercli name: ordercli
description: Foodora-only CLI for checking past orders and active order status (Deliveroo WIP). description: Foodora-only CLI for checking past orders and active order status (Deliveroo WIP).
homepage: https://ordercli.sh homepage: https://ordercli.sh
metadata: {"openclaw":{"emoji":"🛵","requires":{"bins":["ordercli"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/ordercli","bins":["ordercli"],"label":"Install ordercli (brew)"},{"id":"go","kind":"go","module":"github.com/steipete/ordercli/cmd/ordercli@latest","bins":["ordercli"],"label":"Install ordercli (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "🛵",
"requires": { "bins": ["ordercli"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/ordercli",
"bins": ["ordercli"],
"label": "Install ordercli (brew)",
},
{
"id": "go",
"kind": "go",
"module": "github.com/steipete/ordercli/cmd/ordercli@latest",
"bins": ["ordercli"],
"label": "Install ordercli (go)",
},
],
},
}
--- ---
# ordercli # ordercli
@@ -10,6 +34,7 @@ metadata: {"openclaw":{"emoji":"🛵","requires":{"bins":["ordercli"]},"install"
Use `ordercli` to check past orders and track active order status (Foodora only right now). Use `ordercli` to check past orders and track active order status (Foodora only right now).
Quick start (Foodora) Quick start (Foodora)
- `ordercli foodora countries` - `ordercli foodora countries`
- `ordercli foodora config set --country AT` - `ordercli foodora config set --country AT`
- `ordercli foodora login --email you@example.com --password-stdin` - `ordercli foodora login --email you@example.com --password-stdin`
@@ -18,30 +43,36 @@ Quick start (Foodora)
- `ordercli foodora history show <orderCode>` - `ordercli foodora history show <orderCode>`
Orders Orders
- Active list (arrival/status): `ordercli foodora orders` - Active list (arrival/status): `ordercli foodora orders`
- Watch: `ordercli foodora orders --watch` - Watch: `ordercli foodora orders --watch`
- Active order detail: `ordercli foodora order <orderCode>` - Active order detail: `ordercli foodora order <orderCode>`
- History detail JSON: `ordercli foodora history show <orderCode> --json` - History detail JSON: `ordercli foodora history show <orderCode> --json`
Reorder (adds to cart) Reorder (adds to cart)
- Preview: `ordercli foodora reorder <orderCode>` - Preview: `ordercli foodora reorder <orderCode>`
- Confirm: `ordercli foodora reorder <orderCode> --confirm` - Confirm: `ordercli foodora reorder <orderCode> --confirm`
- Address: `ordercli foodora reorder <orderCode> --confirm --address-id <id>` - Address: `ordercli foodora reorder <orderCode> --confirm --address-id <id>`
Cloudflare / bot protection Cloudflare / bot protection
- Browser login: `ordercli foodora login --email you@example.com --password-stdin --browser` - Browser login: `ordercli foodora login --email you@example.com --password-stdin --browser`
- Reuse profile: `--browser-profile "$HOME/Library/Application Support/ordercli/browser-profile"` - Reuse profile: `--browser-profile "$HOME/Library/Application Support/ordercli/browser-profile"`
- Import Chrome cookies: `ordercli foodora cookies chrome --profile "Default"` - Import Chrome cookies: `ordercli foodora cookies chrome --profile "Default"`
Session import (no password) Session import (no password)
- `ordercli foodora session chrome --url https://www.foodora.at/ --profile "Default"` - `ordercli foodora session chrome --url https://www.foodora.at/ --profile "Default"`
- `ordercli foodora session refresh --client-id android` - `ordercli foodora session refresh --client-id android`
Deliveroo (WIP, not working yet) Deliveroo (WIP, not working yet)
- Requires `DELIVEROO_BEARER_TOKEN` (optional `DELIVEROO_COOKIE`). - Requires `DELIVEROO_BEARER_TOKEN` (optional `DELIVEROO_COOKIE`).
- `ordercli deliveroo config set --market uk` - `ordercli deliveroo config set --market uk`
- `ordercli deliveroo history` - `ordercli deliveroo history`
Notes Notes
- Use `--config /tmp/ordercli.json` for testing. - Use `--config /tmp/ordercli.json` for testing.
- Confirm before any reorder or cart-changing action. - Confirm before any reorder or cart-changing action.

View File

@@ -2,7 +2,25 @@
name: peekaboo name: peekaboo
description: Capture and automate macOS UI with the Peekaboo CLI. description: Capture and automate macOS UI with the Peekaboo CLI.
homepage: https://peekaboo.boo homepage: https://peekaboo.boo
metadata: {"openclaw":{"emoji":"👀","os":["darwin"],"requires":{"bins":["peekaboo"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/peekaboo","bins":["peekaboo"],"label":"Install Peekaboo (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "👀",
"os": ["darwin"],
"requires": { "bins": ["peekaboo"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/peekaboo",
"bins": ["peekaboo"],
"label": "Install Peekaboo (brew)",
},
],
},
}
--- ---
# Peekaboo # Peekaboo
@@ -16,6 +34,7 @@ Tip: run via `polter peekaboo` to ensure fresh builds.
## Features (all CLI capabilities, excluding agent/MCP) ## Features (all CLI capabilities, excluding agent/MCP)
Core Core
- `bridge`: inspect Peekaboo Bridge host connectivity - `bridge`: inspect Peekaboo Bridge host connectivity
- `capture`: live capture or video ingest + frame extraction - `capture`: live capture or video ingest + frame extraction
- `clean`: prune snapshot cache and temp files - `clean`: prune snapshot cache and temp files
@@ -29,6 +48,7 @@ Core
- `tools`: list available tools with filtering/display options - `tools`: list available tools with filtering/display options
Interaction Interaction
- `click`: target by ID/query/coords with smart waits - `click`: target by ID/query/coords with smart waits
- `drag`: drag & drop across elements/coords/Dock - `drag`: drag & drop across elements/coords/Dock
- `hotkey`: modifier combos like `cmd,shift,t` - `hotkey`: modifier combos like `cmd,shift,t`
@@ -40,6 +60,7 @@ Interaction
- `type`: text + control keys (`--clear`, delays) - `type`: text + control keys (`--clear`, delays)
System System
- `app`: launch/quit/relaunch/hide/unhide/switch/list apps - `app`: launch/quit/relaunch/hide/unhide/switch/list apps
- `clipboard`: read/write clipboard (text/images/files) - `clipboard`: read/write clipboard (text/images/files)
- `dialog`: click/input/file/dismiss/list system dialogs - `dialog`: click/input/file/dismiss/list system dialogs
@@ -52,13 +73,16 @@ System
- `window`: close/minimize/maximize/move/resize/focus/list - `window`: close/minimize/maximize/move/resize/focus/list
Vision Vision
- `see`: annotated UI maps, snapshot IDs, optional analysis - `see`: annotated UI maps, snapshot IDs, optional analysis
Global runtime flags Global runtime flags
- `--json`/`-j`, `--verbose`/`-v`, `--log-level <level>` - `--json`/`-j`, `--verbose`/`-v`, `--log-level <level>`
- `--no-remote`, `--bridge-socket <path>` - `--no-remote`, `--bridge-socket <path>`
## Quickstart (happy path) ## Quickstart (happy path)
```bash ```bash
peekaboo permissions peekaboo permissions
peekaboo list apps --json peekaboo list apps --json
@@ -68,6 +92,7 @@ peekaboo type "Hello" --return
``` ```
## Common targeting parameters (most interaction commands) ## Common targeting parameters (most interaction commands)
- App/window: `--app`, `--pid`, `--window-title`, `--window-id`, `--window-index` - App/window: `--app`, `--pid`, `--window-title`, `--window-id`, `--window-index`
- Snapshot targeting: `--snapshot` (ID from `see`; defaults to latest) - Snapshot targeting: `--snapshot` (ID from `see`; defaults to latest)
- Element/coords: `--on`/`--id` (element ID), `--coords x,y` - Element/coords: `--on`/`--id` (element ID), `--coords x,y`
@@ -75,6 +100,7 @@ peekaboo type "Hello" --return
`--focus-timeout-seconds`, `--focus-retry-count` `--focus-timeout-seconds`, `--focus-retry-count`
## Common capture parameters ## Common capture parameters
- Output: `--path`, `--format png|jpg`, `--retina` - Output: `--path`, `--format png|jpg`, `--retina`
- Targeting: `--mode screen|window|frontmost`, `--screen-index`, - Targeting: `--mode screen|window|frontmost`, `--screen-index`,
`--window-title`, `--window-id` `--window-title`, `--window-id`
@@ -82,12 +108,15 @@ peekaboo type "Hello" --return
- Capture engine: `--capture-engine auto|classic|cg|modern|sckit` - Capture engine: `--capture-engine auto|classic|cg|modern|sckit`
## Common motion/typing parameters ## Common motion/typing parameters
- Timing: `--duration` (drag/swipe), `--steps`, `--delay` (type/scroll/press) - Timing: `--duration` (drag/swipe), `--steps`, `--delay` (type/scroll/press)
- Human-ish movement: `--profile human|linear`, `--wpm` (typing) - Human-ish movement: `--profile human|linear`, `--wpm` (typing)
- Scroll: `--direction up|down|left|right`, `--amount <ticks>`, `--smooth` - Scroll: `--direction up|down|left|right`, `--amount <ticks>`, `--smooth`
## Examples ## Examples
### See -> click -> type (most reliable flow) ### See -> click -> type (most reliable flow)
```bash ```bash
peekaboo see --app Safari --window-title "Login" --annotate --path /tmp/see.png peekaboo see --app Safari --window-title "Login" --annotate --path /tmp/see.png
peekaboo click --on B3 --app Safari peekaboo click --on B3 --app Safari
@@ -97,6 +126,7 @@ peekaboo type "supersecret" --app Safari --return
``` ```
### Target by window id ### Target by window id
```bash ```bash
peekaboo list windows --app "Visual Studio Code" --json peekaboo list windows --app "Visual Studio Code" --json
peekaboo click --window-id 12345 --coords 120,160 peekaboo click --window-id 12345 --coords 120,160
@@ -104,6 +134,7 @@ peekaboo type "Hello from Peekaboo" --window-id 12345
``` ```
### Capture screenshots + analyze ### Capture screenshots + analyze
```bash ```bash
peekaboo image --mode screen --screen-index 0 --retina --path /tmp/screen.png peekaboo image --mode screen --screen-index 0 --retina --path /tmp/screen.png
peekaboo image --app Safari --window-title "Dashboard" --analyze "Summarize KPIs" peekaboo image --app Safari --window-title "Dashboard" --analyze "Summarize KPIs"
@@ -111,12 +142,14 @@ peekaboo see --mode screen --screen-index 0 --analyze "Summarize the dashboard"
``` ```
### Live capture (motion-aware) ### Live capture (motion-aware)
```bash ```bash
peekaboo capture live --mode region --region 100,100,800,600 --duration 30 \ peekaboo capture live --mode region --region 100,100,800,600 --duration 30 \
--active-fps 8 --idle-fps 2 --highlight-changes --path /tmp/capture --active-fps 8 --idle-fps 2 --highlight-changes --path /tmp/capture
``` ```
### App + window management ### App + window management
```bash ```bash
peekaboo app launch "Safari" --open https://example.com peekaboo app launch "Safari" --open https://example.com
peekaboo window focus --app Safari --window-title "Example" peekaboo window focus --app Safari --window-title "Example"
@@ -125,6 +158,7 @@ peekaboo app quit --app Safari
``` ```
### Menus, menubar, dock ### Menus, menubar, dock
```bash ```bash
peekaboo menu click --app Safari --item "New Window" peekaboo menu click --app Safari --item "New Window"
peekaboo menu click --app TextEdit --path "Format > Font > Show Fonts" peekaboo menu click --app TextEdit --path "Format > Font > Show Fonts"
@@ -134,6 +168,7 @@ peekaboo menubar list --json
``` ```
### Mouse + gesture input ### Mouse + gesture input
```bash ```bash
peekaboo move 500,300 --smooth peekaboo move 500,300 --smooth
peekaboo drag --from B1 --to T2 peekaboo drag --from B1 --to T2
@@ -142,6 +177,7 @@ peekaboo scroll --direction down --amount 6 --smooth
``` ```
### Keyboard input ### Keyboard input
```bash ```bash
peekaboo hotkey --keys "cmd,shift,t" peekaboo hotkey --keys "cmd,shift,t"
peekaboo press escape peekaboo press escape
@@ -149,5 +185,6 @@ peekaboo type "Line 1\nLine 2" --delay 10
``` ```
Notes Notes
- Requires Screen Recording + Accessibility permissions. - Requires Screen Recording + Accessibility permissions.
- Use `peekaboo see --annotate` to identify targets before clicking. - Use `peekaboo see --annotate` to identify targets before clicking.

View File

@@ -2,7 +2,25 @@
name: sag name: sag
description: ElevenLabs text-to-speech with mac-style say UX. description: ElevenLabs text-to-speech with mac-style say UX.
homepage: https://sag.sh homepage: https://sag.sh
metadata: {"openclaw":{"emoji":"🗣️","requires":{"bins":["sag"],"env":["ELEVENLABS_API_KEY"]},"primaryEnv":"ELEVENLABS_API_KEY","install":[{"id":"brew","kind":"brew","formula":"steipete/tap/sag","bins":["sag"],"label":"Install sag (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🗣️",
"requires": { "bins": ["sag"], "env": ["ELEVENLABS_API_KEY"] },
"primaryEnv": "ELEVENLABS_API_KEY",
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/sag",
"bins": ["sag"],
"label": "Install sag (brew)",
},
],
},
}
--- ---
# sag # sag
@@ -10,21 +28,25 @@ metadata: {"openclaw":{"emoji":"🗣️","requires":{"bins":["sag"],"env":["ELEV
Use `sag` for ElevenLabs TTS with local playback. Use `sag` for ElevenLabs TTS with local playback.
API key (required) API key (required)
- `ELEVENLABS_API_KEY` (preferred) - `ELEVENLABS_API_KEY` (preferred)
- `SAG_API_KEY` also supported by the CLI - `SAG_API_KEY` also supported by the CLI
Quick start Quick start
- `sag "Hello there"` - `sag "Hello there"`
- `sag speak -v "Roger" "Hello"` - `sag speak -v "Roger" "Hello"`
- `sag voices` - `sag voices`
- `sag prompting` (model-specific tips) - `sag prompting` (model-specific tips)
Model notes Model notes
- Default: `eleven_v3` (expressive) - Default: `eleven_v3` (expressive)
- Stable: `eleven_multilingual_v2` - Stable: `eleven_multilingual_v2`
- Fast: `eleven_flash_v2_5` - Fast: `eleven_flash_v2_5`
Pronunciation + delivery rules Pronunciation + delivery rules
- First fix: respell (e.g. "key-note"), add hyphens, adjust casing. - First fix: respell (e.g. "key-note"), add hyphens, adjust casing.
- Numbers/units/URLs: `--normalize auto` (or `off` if it harms names). - Numbers/units/URLs: `--normalize auto` (or `off` if it harms names).
- Language bias: `--lang en|de|fr|...` to guide normalization. - Language bias: `--lang en|de|fr|...` to guide normalization.
@@ -32,12 +54,14 @@ Pronunciation + delivery rules
- v2/v2.5: SSML `<break time="1.5s" />` supported; `<phoneme>` not exposed in `sag`. - v2/v2.5: SSML `<break time="1.5s" />` supported; `<phoneme>` not exposed in `sag`.
v3 audio tags (put at the entrance of a line) v3 audio tags (put at the entrance of a line)
- `[whispers]`, `[shouts]`, `[sings]` - `[whispers]`, `[shouts]`, `[sings]`
- `[laughs]`, `[starts laughing]`, `[sighs]`, `[exhales]` - `[laughs]`, `[starts laughing]`, `[sighs]`, `[exhales]`
- `[sarcastic]`, `[curious]`, `[excited]`, `[crying]`, `[mischievously]` - `[sarcastic]`, `[curious]`, `[excited]`, `[crying]`, `[mischievously]`
- Example: `sag "[whispers] keep this quiet. [short pause] ok?"` - Example: `sag "[whispers] keep this quiet. [short pause] ok?"`
Voice defaults Voice defaults
- `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID` - `ELEVENLABS_VOICE_ID` or `SAG_VOICE_ID`
Confirm voice + speaker before long output. Confirm voice + speaker before long output.
@@ -55,6 +79,7 @@ sag -v Clawd -o /tmp/voice-reply.mp3 "Your message here"
``` ```
Voice character tips: Voice character tips:
- Crazy scientist: Use `[excited]` tags, dramatic pauses `[short pause]`, vary intensity - Crazy scientist: Use `[excited]` tags, dramatic pauses `[short pause]`, vary intensity
- Calm: Use `[whispers]` or slower pacing - Calm: Use `[whispers]` or slower pacing
- Dramatic: Use `[sings]` or `[shouts]` sparingly - Dramatic: Use `[sings]` or `[shouts]` sparingly

View File

@@ -1,7 +1,7 @@
--- ---
name: session-logs name: session-logs
description: Search and analyze your own session logs (older/parent conversations) using jq. description: Search and analyze your own session logs (older/parent conversations) using jq.
metadata: {"openclaw":{"emoji":"📜","requires":{"bins":["jq","rg"]}}} metadata: { "openclaw": { "emoji": "📜", "requires": { "bins": ["jq", "rg"] } } }
--- ---
# session-logs # session-logs
@@ -22,6 +22,7 @@ Session logs live at: `~/.openclaw/agents/<agentId>/sessions/` (use the `agent=<
## Structure ## Structure
Each `.jsonl` file contains messages with: Each `.jsonl` file contains messages with:
- `type`: "session" (metadata) or "message" - `type`: "session" (metadata) or "message"
- `timestamp`: ISO timestamp - `timestamp`: ISO timestamp
- `message.role`: "user", "assistant", or "toolResult" - `message.role`: "user", "assistant", or "toolResult"
@@ -31,6 +32,7 @@ Each `.jsonl` file contains messages with:
## Common Queries ## Common Queries
### List all sessions by date and size ### List all sessions by date and size
```bash ```bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1) date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1)
@@ -40,6 +42,7 @@ done | sort -r
``` ```
### Find sessions from a specific day ### Find sessions from a specific day
```bash ```bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
head -1 "$f" | jq -r '.timestamp' | grep -q "2026-01-06" && echo "$f" head -1 "$f" | jq -r '.timestamp' | grep -q "2026-01-06" && echo "$f"
@@ -47,21 +50,25 @@ done
``` ```
### Extract user messages from a session ### Extract user messages from a session
```bash ```bash
jq -r 'select(.message.role == "user") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl jq -r 'select(.message.role == "user") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl
``` ```
### Search for keyword in assistant responses ### Search for keyword in assistant responses
```bash ```bash
jq -r 'select(.message.role == "assistant") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl | rg -i "keyword" jq -r 'select(.message.role == "assistant") | .message.content[]? | select(.type == "text") | .text' <session>.jsonl | rg -i "keyword"
``` ```
### Get total cost for a session ### Get total cost for a session
```bash ```bash
jq -s '[.[] | .message.usage.cost.total // 0] | add' <session>.jsonl jq -s '[.[] | .message.usage.cost.total // 0] | add' <session>.jsonl
``` ```
### Daily cost summary ### Daily cost summary
```bash ```bash
for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do for f in ~/.openclaw/agents/<agentId>/sessions/*.jsonl; do
date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1) date=$(head -1 "$f" | jq -r '.timestamp' | cut -dT -f1)
@@ -71,6 +78,7 @@ done | awk '{a[$1]+=$2} END {for(d in a) print d, "$"a[d]}' | sort -r
``` ```
### Count messages and tokens in a session ### Count messages and tokens in a session
```bash ```bash
jq -s '{ jq -s '{
messages: length, messages: length,
@@ -82,11 +90,13 @@ jq -s '{
``` ```
### Tool usage breakdown ### Tool usage breakdown
```bash ```bash
jq -r '.message.content[]? | select(.type == "toolCall") | .name' <session>.jsonl | sort | uniq -c | sort -rn jq -r '.message.content[]? | select(.type == "toolCall") | .name' <session>.jsonl | sort | uniq -c | sort -rn
``` ```
### Search across ALL sessions for a phrase ### Search across ALL sessions for a phrase
```bash ```bash
rg -l "phrase" ~/.openclaw/agents/<agentId>/sessions/*.jsonl rg -l "phrase" ~/.openclaw/agents/<agentId>/sessions/*.jsonl
``` ```

View File

@@ -1,7 +1,60 @@
--- ---
name: sherpa-onnx-tts name: sherpa-onnx-tts
description: Local text-to-speech via sherpa-onnx (offline, no cloud) description: Local text-to-speech via sherpa-onnx (offline, no cloud)
metadata: {"openclaw":{"emoji":"🗣️","os":["darwin","linux","win32"],"requires":{"env":["SHERPA_ONNX_RUNTIME_DIR","SHERPA_ONNX_MODEL_DIR"]},"install":[{"id":"download-runtime-macos","kind":"download","os":["darwin"],"url":"https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-osx-universal2-shared.tar.bz2","archive":"tar.bz2","extract":true,"stripComponents":1,"targetDir":"~/.openclaw/tools/sherpa-onnx-tts/runtime","label":"Download sherpa-onnx runtime (macOS)"},{"id":"download-runtime-linux-x64","kind":"download","os":["linux"],"url":"https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-linux-x64-shared.tar.bz2","archive":"tar.bz2","extract":true,"stripComponents":1,"targetDir":"~/.openclaw/tools/sherpa-onnx-tts/runtime","label":"Download sherpa-onnx runtime (Linux x64)"},{"id":"download-runtime-win-x64","kind":"download","os":["win32"],"url":"https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-win-x64-shared.tar.bz2","archive":"tar.bz2","extract":true,"stripComponents":1,"targetDir":"~/.openclaw/tools/sherpa-onnx-tts/runtime","label":"Download sherpa-onnx runtime (Windows x64)"},{"id":"download-model-lessac","kind":"download","url":"https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_US-lessac-high.tar.bz2","archive":"tar.bz2","extract":true,"targetDir":"~/.openclaw/tools/sherpa-onnx-tts/models","label":"Download Piper en_US lessac (high)"}]}} metadata:
{
"openclaw":
{
"emoji": "🗣️",
"os": ["darwin", "linux", "win32"],
"requires": { "env": ["SHERPA_ONNX_RUNTIME_DIR", "SHERPA_ONNX_MODEL_DIR"] },
"install":
[
{
"id": "download-runtime-macos",
"kind": "download",
"os": ["darwin"],
"url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-osx-universal2-shared.tar.bz2",
"archive": "tar.bz2",
"extract": true,
"stripComponents": 1,
"targetDir": "~/.openclaw/tools/sherpa-onnx-tts/runtime",
"label": "Download sherpa-onnx runtime (macOS)",
},
{
"id": "download-runtime-linux-x64",
"kind": "download",
"os": ["linux"],
"url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-linux-x64-shared.tar.bz2",
"archive": "tar.bz2",
"extract": true,
"stripComponents": 1,
"targetDir": "~/.openclaw/tools/sherpa-onnx-tts/runtime",
"label": "Download sherpa-onnx runtime (Linux x64)",
},
{
"id": "download-runtime-win-x64",
"kind": "download",
"os": ["win32"],
"url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-win-x64-shared.tar.bz2",
"archive": "tar.bz2",
"extract": true,
"stripComponents": 1,
"targetDir": "~/.openclaw/tools/sherpa-onnx-tts/runtime",
"label": "Download sherpa-onnx runtime (Windows x64)",
},
{
"id": "download-model-lessac",
"kind": "download",
"url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_US-lessac-high.tar.bz2",
"archive": "tar.bz2",
"extract": true,
"targetDir": "~/.openclaw/tools/sherpa-onnx-tts/models",
"label": "Download Piper en_US lessac (high)",
},
],
},
}
--- ---
# sherpa-onnx-tts # sherpa-onnx-tts
@@ -10,8 +63,8 @@ Local TTS using the sherpa-onnx offline CLI.
## Install ## Install
1) Download the runtime for your OS (extracts into `~/.openclaw/tools/sherpa-onnx-tts/runtime`) 1. Download the runtime for your OS (extracts into `~/.openclaw/tools/sherpa-onnx-tts/runtime`)
2) Download a voice model (extracts into `~/.openclaw/tools/sherpa-onnx-tts/models`) 2. Download a voice model (extracts into `~/.openclaw/tools/sherpa-onnx-tts/models`)
Update `~/.openclaw/openclaw.json`: Update `~/.openclaw/openclaw.json`:
@@ -22,11 +75,11 @@ Update `~/.openclaw/openclaw.json`:
"sherpa-onnx-tts": { "sherpa-onnx-tts": {
env: { env: {
SHERPA_ONNX_RUNTIME_DIR: "~/.openclaw/tools/sherpa-onnx-tts/runtime", SHERPA_ONNX_RUNTIME_DIR: "~/.openclaw/tools/sherpa-onnx-tts/runtime",
SHERPA_ONNX_MODEL_DIR: "~/.openclaw/tools/sherpa-onnx-tts/models/vits-piper-en_US-lessac-high" SHERPA_ONNX_MODEL_DIR: "~/.openclaw/tools/sherpa-onnx-tts/models/vits-piper-en_US-lessac-high",
} },
} },
} },
} },
} }
``` ```
@@ -43,6 +96,7 @@ export PATH="{baseDir}/bin:$PATH"
``` ```
Notes: Notes:
- Pick a different model from the sherpa-onnx `tts-models` release if you want another voice. - Pick a different model from the sherpa-onnx `tts-models` release if you want another voice.
- If the model dir has multiple `.onnx` files, set `SHERPA_ONNX_MODEL_FILE` or pass `--model-file`. - If the model dir has multiple `.onnx` files, set `SHERPA_ONNX_MODEL_FILE` or pass `--model-file`.
- You can also pass `--tokens-file` or `--data-dir` to override the defaults. - You can also pass `--tokens-file` or `--data-dir` to override the defaults.

View File

@@ -349,7 +349,6 @@ scripts/package_skill.py <path/to/skill-folder> ./dist
The packaging script will: The packaging script will:
1. **Validate** the skill automatically, checking: 1. **Validate** the skill automatically, checking:
- YAML frontmatter format and required fields - YAML frontmatter format and required fields
- Skill naming conventions and directory structure - Skill naming conventions and directory structure
- Description completeness and quality - Description completeness and quality

View File

@@ -1,7 +1,7 @@
--- ---
name: slack name: slack
description: Use when you need to control Slack from OpenClaw via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs. description: Use when you need to control Slack from OpenClaw via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
metadata: {"openclaw":{"emoji":"💬","requires":{"config":["channels.slack"]}}} metadata: { "openclaw": { "emoji": "💬", "requires": { "config": ["channels.slack"] } } }
--- ---
# Slack Actions # Slack Actions
@@ -22,13 +22,13 @@ Message context lines include `slack message id` and `channel` fields you can re
### Action groups ### Action groups
| Action group | Default | Notes | | Action group | Default | Notes |
| --- | --- | --- | | ------------ | ------- | ---------------------- |
| reactions | enabled | React + list reactions | | reactions | enabled | React + list reactions |
| messages | enabled | Read/send/edit/delete | | messages | enabled | Read/send/edit/delete |
| pins | enabled | Pin/unpin/list | | pins | enabled | Pin/unpin/list |
| memberInfo | enabled | Member info | | memberInfo | enabled | Member info |
| emojiList | enabled | Custom emoji list | | emojiList | enabled | Custom emoji list |
### React to a message ### React to a message

View File

@@ -2,7 +2,24 @@
name: songsee name: songsee
description: Generate spectrograms and feature-panel visualizations from audio with the songsee CLI. description: Generate spectrograms and feature-panel visualizations from audio with the songsee CLI.
homepage: https://github.com/steipete/songsee homepage: https://github.com/steipete/songsee
metadata: {"openclaw":{"emoji":"🌊","requires":{"bins":["songsee"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/songsee","bins":["songsee"],"label":"Install songsee (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🌊",
"requires": { "bins": ["songsee"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/songsee",
"bins": ["songsee"],
"label": "Install songsee (brew)",
},
],
},
}
--- ---
# songsee # songsee
@@ -10,12 +27,14 @@ metadata: {"openclaw":{"emoji":"🌊","requires":{"bins":["songsee"]},"install":
Generate spectrograms + feature panels from audio. Generate spectrograms + feature panels from audio.
Quick start Quick start
- Spectrogram: `songsee track.mp3` - Spectrogram: `songsee track.mp3`
- Multi-panel: `songsee track.mp3 --viz spectrogram,mel,chroma,hpss,selfsim,loudness,tempogram,mfcc,flux` - Multi-panel: `songsee track.mp3 --viz spectrogram,mel,chroma,hpss,selfsim,loudness,tempogram,mfcc,flux`
- Time slice: `songsee track.mp3 --start 12.5 --duration 8 -o slice.jpg` - Time slice: `songsee track.mp3 --start 12.5 --duration 8 -o slice.jpg`
- Stdin: `cat track.mp3 | songsee - --format png -o out.png` - Stdin: `cat track.mp3 | songsee - --format png -o out.png`
Common flags Common flags
- `--viz` list (repeatable or comma-separated) - `--viz` list (repeatable or comma-separated)
- `--style` palette (classic, magma, inferno, viridis, gray) - `--style` palette (classic, magma, inferno, viridis, gray)
- `--width` / `--height` output size - `--width` / `--height` output size
@@ -25,5 +44,6 @@ Common flags
- `--format` jpg|png - `--format` jpg|png
Notes Notes
- WAV/MP3 decode native; other formats use ffmpeg if available. - WAV/MP3 decode native; other formats use ffmpeg if available.
- Multiple `--viz` renders a grid. - Multiple `--viz` renders a grid.

View File

@@ -2,7 +2,24 @@
name: sonoscli name: sonoscli
description: Control Sonos speakers (discover/status/play/volume/group). description: Control Sonos speakers (discover/status/play/volume/group).
homepage: https://sonoscli.sh homepage: https://sonoscli.sh
metadata: {"openclaw":{"emoji":"🔊","requires":{"bins":["sonos"]},"install":[{"id":"go","kind":"go","module":"github.com/steipete/sonoscli/cmd/sonos@latest","bins":["sonos"],"label":"Install sonoscli (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "🔊",
"requires": { "bins": ["sonos"] },
"install":
[
{
"id": "go",
"kind": "go",
"module": "github.com/steipete/sonoscli/cmd/sonos@latest",
"bins": ["sonos"],
"label": "Install sonoscli (go)",
},
],
},
}
--- ---
# Sonos CLI # Sonos CLI
@@ -10,17 +27,20 @@ metadata: {"openclaw":{"emoji":"🔊","requires":{"bins":["sonos"]},"install":[{
Use `sonos` to control Sonos speakers on the local network. Use `sonos` to control Sonos speakers on the local network.
Quick start Quick start
- `sonos discover` - `sonos discover`
- `sonos status --name "Kitchen"` - `sonos status --name "Kitchen"`
- `sonos play|pause|stop --name "Kitchen"` - `sonos play|pause|stop --name "Kitchen"`
- `sonos volume set 15 --name "Kitchen"` - `sonos volume set 15 --name "Kitchen"`
Common tasks Common tasks
- Grouping: `sonos group status|join|unjoin|party|solo` - Grouping: `sonos group status|join|unjoin|party|solo`
- Favorites: `sonos favorites list|open` - Favorites: `sonos favorites list|open`
- Queue: `sonos queue list|play|clear` - Queue: `sonos queue list|play|clear`
- Spotify search (via SMAPI): `sonos smapi search --service "Spotify" --category tracks "query"` - Spotify search (via SMAPI): `sonos smapi search --service "Spotify" --category tracks "query"`
Notes Notes
- If SSDP fails, specify `--ip <speaker-ip>`. - If SSDP fails, specify `--ip <speaker-ip>`.
- Spotify Web API search is optional and requires `SPOTIFY_CLIENT_ID/SECRET`. - Spotify Web API search is optional and requires `SPOTIFY_CLIENT_ID/SECRET`.

View File

@@ -2,7 +2,32 @@
name: spotify-player name: spotify-player
description: Terminal Spotify playback/search via spogo (preferred) or spotify_player. description: Terminal Spotify playback/search via spogo (preferred) or spotify_player.
homepage: https://www.spotify.com homepage: https://www.spotify.com
metadata: {"openclaw":{"emoji":"🎵","requires":{"anyBins":["spogo","spotify_player"]},"install":[{"id":"brew","kind":"brew","formula":"spogo","tap":"steipete/tap","bins":["spogo"],"label":"Install spogo (brew)"},{"id":"brew","kind":"brew","formula":"spotify_player","bins":["spotify_player"],"label":"Install spotify_player (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🎵",
"requires": { "anyBins": ["spogo", "spotify_player"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "spogo",
"tap": "steipete/tap",
"bins": ["spogo"],
"label": "Install spogo (brew)",
},
{
"id": "brew",
"kind": "brew",
"formula": "spotify_player",
"bins": ["spotify_player"],
"label": "Install spotify_player (brew)",
},
],
},
}
--- ---
# spogo / spotify_player # spogo / spotify_player
@@ -10,25 +35,30 @@ metadata: {"openclaw":{"emoji":"🎵","requires":{"anyBins":["spogo","spotify_pl
Use `spogo` **(preferred)** for Spotify playback/search. Fall back to `spotify_player` if needed. Use `spogo` **(preferred)** for Spotify playback/search. Fall back to `spotify_player` if needed.
Requirements Requirements
- Spotify Premium account. - Spotify Premium account.
- Either `spogo` or `spotify_player` installed. - Either `spogo` or `spotify_player` installed.
spogo setup spogo setup
- Import cookies: `spogo auth import --browser chrome` - Import cookies: `spogo auth import --browser chrome`
Common CLI commands Common CLI commands
- Search: `spogo search track "query"` - Search: `spogo search track "query"`
- Playback: `spogo play|pause|next|prev` - Playback: `spogo play|pause|next|prev`
- Devices: `spogo device list`, `spogo device set "<name|id>"` - Devices: `spogo device list`, `spogo device set "<name|id>"`
- Status: `spogo status` - Status: `spogo status`
spotify_player commands (fallback) spotify_player commands (fallback)
- Search: `spotify_player search "query"` - Search: `spotify_player search "query"`
- Playback: `spotify_player playback play|pause|next|previous` - Playback: `spotify_player playback play|pause|next|previous`
- Connect device: `spotify_player connect` - Connect device: `spotify_player connect`
- Like track: `spotify_player like` - Like track: `spotify_player like`
Notes Notes
- Config folder: `~/.config/spotify-player` (e.g., `app.toml`). - Config folder: `~/.config/spotify-player` (e.g., `app.toml`).
- For Spotify Connect integration, set a user `client_id` in config. - For Spotify Connect integration, set a user `client_id` in config.
- TUI shortcuts are available via `?` in the app. - TUI shortcuts are available via `?` in the app.

View File

@@ -2,7 +2,24 @@
name: summarize name: summarize
description: Summarize or extract text/transcripts from URLs, podcasts, and local files (great fallback for “transcribe this YouTube/video”). description: Summarize or extract text/transcripts from URLs, podcasts, and local files (great fallback for “transcribe this YouTube/video”).
homepage: https://summarize.sh homepage: https://summarize.sh
metadata: {"openclaw":{"emoji":"🧾","requires":{"bins":["summarize"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/summarize","bins":["summarize"],"label":"Install summarize (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🧾",
"requires": { "bins": ["summarize"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/summarize",
"bins": ["summarize"],
"label": "Install summarize (brew)",
},
],
},
}
--- ---
# Summarize # Summarize
@@ -12,6 +29,7 @@ Fast CLI to summarize URLs, local files, and YouTube links.
## When to use (trigger phrases) ## When to use (trigger phrases)
Use this skill immediately when the user asks any of: Use this skill immediately when the user asks any of:
- “use summarize.sh” - “use summarize.sh”
- “whats this link/video about?” - “whats this link/video about?”
- “summarize this URL/article” - “summarize this URL/article”
@@ -38,6 +56,7 @@ If the user asked for a transcript but its huge, return a tight summary first
## Model + keys ## Model + keys
Set the API key for your chosen provider: Set the API key for your chosen provider:
- OpenAI: `OPENAI_API_KEY` - OpenAI: `OPENAI_API_KEY`
- Anthropic: `ANTHROPIC_API_KEY` - Anthropic: `ANTHROPIC_API_KEY`
- xAI: `XAI_API_KEY` - xAI: `XAI_API_KEY`
@@ -63,5 +82,6 @@ Optional config file: `~/.summarize/config.json`
``` ```
Optional services: Optional services:
- `FIRECRAWL_API_KEY` for blocked sites - `FIRECRAWL_API_KEY` for blocked sites
- `APIFY_API_TOKEN` for YouTube fallback - `APIFY_API_TOKEN` for YouTube fallback

View File

@@ -2,7 +2,25 @@
name: things-mac name: things-mac
description: Manage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database). Use when a user asks OpenClaw to add a task to Things, list inbox/today/upcoming, search tasks, or inspect projects/areas/tags. description: Manage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database). Use when a user asks OpenClaw to add a task to Things, list inbox/today/upcoming, search tasks, or inspect projects/areas/tags.
homepage: https://github.com/ossianhempel/things3-cli homepage: https://github.com/ossianhempel/things3-cli
metadata: {"openclaw":{"emoji":"✅","os":["darwin"],"requires":{"bins":["things"]},"install":[{"id":"go","kind":"go","module":"github.com/ossianhempel/things3-cli/cmd/things@latest","bins":["things"],"label":"Install things3-cli (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "✅",
"os": ["darwin"],
"requires": { "bins": ["things"] },
"install":
[
{
"id": "go",
"kind": "go",
"module": "github.com/ossianhempel/things3-cli/cmd/things@latest",
"bins": ["things"],
"label": "Install things3-cli (go)",
},
],
},
}
--- ---
# Things 3 CLI # Things 3 CLI
@@ -10,12 +28,14 @@ metadata: {"openclaw":{"emoji":"✅","os":["darwin"],"requires":{"bins":["things
Use `things` to read your local Things database (inbox/today/search/projects/areas/tags) and to add/update todos via the Things URL scheme. Use `things` to read your local Things database (inbox/today/search/projects/areas/tags) and to add/update todos via the Things URL scheme.
Setup Setup
- Install (recommended, Apple Silicon): `GOBIN=/opt/homebrew/bin go install github.com/ossianhempel/things3-cli/cmd/things@latest` - Install (recommended, Apple Silicon): `GOBIN=/opt/homebrew/bin go install github.com/ossianhempel/things3-cli/cmd/things@latest`
- If DB reads fail: grant **Full Disk Access** to the calling app (Terminal for manual runs; `OpenClaw.app` for gateway runs). - If DB reads fail: grant **Full Disk Access** to the calling app (Terminal for manual runs; `OpenClaw.app` for gateway runs).
- Optional: set `THINGSDB` (or pass `--db`) to point at your `ThingsData-*` folder. - Optional: set `THINGSDB` (or pass `--db`) to point at your `ThingsData-*` folder.
- Optional: set `THINGS_AUTH_TOKEN` to avoid passing `--auth-token` for update ops. - Optional: set `THINGS_AUTH_TOKEN` to avoid passing `--auth-token` for update ops.
Read-only (DB) Read-only (DB)
- `things inbox --limit 50` - `things inbox --limit 50`
- `things today` - `things today`
- `things upcoming` - `things upcoming`
@@ -23,11 +43,13 @@ Read-only (DB)
- `things projects` / `things areas` / `things tags` - `things projects` / `things areas` / `things tags`
Write (URL scheme) Write (URL scheme)
- Prefer safe preview: `things --dry-run add "Title"` - Prefer safe preview: `things --dry-run add "Title"`
- Add: `things add "Title" --notes "..." --when today --deadline 2026-01-02` - Add: `things add "Title" --notes "..." --when today --deadline 2026-01-02`
- Bring Things to front: `things --foreground add "Title"` - Bring Things to front: `things --foreground add "Title"`
Examples: add a todo Examples: add a todo
- Basic: `things add "Buy milk"` - Basic: `things add "Buy milk"`
- With notes: `things add "Buy milk" --notes "2% + bananas"` - With notes: `things add "Buy milk" --notes "2% + bananas"`
- Into a project/area: `things add "Book flights" --list "Travel"` - Into a project/area: `things add "Book flights" --list "Travel"`
@@ -42,6 +64,7 @@ Examples: add a todo
- `EOF` - `EOF`
Examples: modify a todo (needs auth token) Examples: modify a todo (needs auth token)
- First: get the ID (UUID column): `things search "milk" --limit 5` - First: get the ID (UUID column): `things search "milk" --limit 5`
- Auth: set `THINGS_AUTH_TOKEN` or pass `--auth-token <TOKEN>` - Auth: set `THINGS_AUTH_TOKEN` or pass `--auth-token <TOKEN>`
- Title: `things update --id <UUID> --auth-token <TOKEN> "New title"` - Title: `things update --id <UUID> --auth-token <TOKEN> "New title"`
@@ -53,9 +76,11 @@ Examples: modify a todo (needs auth token)
- Safe preview: `things --dry-run update --id <UUID> --auth-token <TOKEN> --completed` - Safe preview: `things --dry-run update --id <UUID> --auth-token <TOKEN> --completed`
Delete a todo? Delete a todo?
- Not supported by `things3-cli` right now (no “delete/move-to-trash” write command; `things trash` is read-only listing). - Not supported by `things3-cli` right now (no “delete/move-to-trash” write command; `things trash` is read-only listing).
- Options: use Things UI to delete/trash, or mark as `--completed` / `--canceled` via `things update`. - Options: use Things UI to delete/trash, or mark as `--completed` / `--canceled` via `things update`.
Notes Notes
- macOS-only. - macOS-only.
- `--dry-run` prints the URL and does not open Things. - `--dry-run` prints the URL and does not open Things.

View File

@@ -1,7 +1,8 @@
--- ---
name: tmux name: tmux
description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output. description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.
metadata: {"openclaw":{"emoji":"🧵","os":["darwin","linux"],"requires":{"bins":["tmux"]}}} metadata:
{ "openclaw": { "emoji": "🧵", "os": ["darwin", "linux"], "requires": { "bins": ["tmux"] } } }
--- ---
# tmux Skill (OpenClaw) # tmux Skill (OpenClaw)
@@ -95,6 +96,7 @@ tmux -S "$SOCKET" capture-pane -p -t agent-1 -S -500
``` ```
**Tips:** **Tips:**
- Use separate git worktrees for parallel fixes (no branch conflicts) - Use separate git worktrees for parallel fixes (no branch conflicts)
- `pnpm install` first before running codex in fresh clones - `pnpm install` first before running codex in fresh clones
- Check for shell prompt (`` or `$`) to detect completion - Check for shell prompt (`` or `$`) to detect completion

View File

@@ -2,7 +2,11 @@
name: trello name: trello
description: Manage Trello boards, lists, and cards via the Trello REST API. description: Manage Trello boards, lists, and cards via the Trello REST API.
homepage: https://developer.atlassian.com/cloud/trello/rest/ homepage: https://developer.atlassian.com/cloud/trello/rest/
metadata: {"openclaw":{"emoji":"📋","requires":{"bins":["jq"],"env":["TRELLO_API_KEY","TRELLO_TOKEN"]}}} metadata:
{
"openclaw":
{ "emoji": "📋", "requires": { "bins": ["jq"], "env": ["TRELLO_API_KEY", "TRELLO_TOKEN"] } },
}
--- ---
# Trello Skill # Trello Skill
@@ -24,21 +28,25 @@ Manage Trello boards, lists, and cards directly from OpenClaw.
All commands use curl to hit the Trello REST API. All commands use curl to hit the Trello REST API.
### List boards ### List boards
```bash ```bash
curl -s "https://api.trello.com/1/members/me/boards?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" | jq '.[] | {name, id}' curl -s "https://api.trello.com/1/members/me/boards?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" | jq '.[] | {name, id}'
``` ```
### List lists in a board ### List lists in a board
```bash ```bash
curl -s "https://api.trello.com/1/boards/{boardId}/lists?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" | jq '.[] | {name, id}' curl -s "https://api.trello.com/1/boards/{boardId}/lists?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" | jq '.[] | {name, id}'
``` ```
### List cards in a list ### List cards in a list
```bash ```bash
curl -s "https://api.trello.com/1/lists/{listId}/cards?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" | jq '.[] | {name, id, desc}' curl -s "https://api.trello.com/1/lists/{listId}/cards?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" | jq '.[] | {name, id, desc}'
``` ```
### Create a card ### Create a card
```bash ```bash
curl -s -X POST "https://api.trello.com/1/cards?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \ curl -s -X POST "https://api.trello.com/1/cards?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
-d "idList={listId}" \ -d "idList={listId}" \
@@ -47,18 +55,21 @@ curl -s -X POST "https://api.trello.com/1/cards?key=$TRELLO_API_KEY&token=$TRELL
``` ```
### Move a card to another list ### Move a card to another list
```bash ```bash
curl -s -X PUT "https://api.trello.com/1/cards/{cardId}?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \ curl -s -X PUT "https://api.trello.com/1/cards/{cardId}?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
-d "idList={newListId}" -d "idList={newListId}"
``` ```
### Add a comment to a card ### Add a comment to a card
```bash ```bash
curl -s -X POST "https://api.trello.com/1/cards/{cardId}/actions/comments?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \ curl -s -X POST "https://api.trello.com/1/cards/{cardId}/actions/comments?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
-d "text=Your comment here" -d "text=Your comment here"
``` ```
### Archive a card ### Archive a card
```bash ```bash
curl -s -X PUT "https://api.trello.com/1/cards/{cardId}?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \ curl -s -X PUT "https://api.trello.com/1/cards/{cardId}?key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
-d "closed=true" -d "closed=true"

View File

@@ -2,7 +2,24 @@
name: video-frames name: video-frames
description: Extract frames or short clips from videos using ffmpeg. description: Extract frames or short clips from videos using ffmpeg.
homepage: https://ffmpeg.org homepage: https://ffmpeg.org
metadata: {"openclaw":{"emoji":"🎞️","requires":{"bins":["ffmpeg"]},"install":[{"id":"brew","kind":"brew","formula":"ffmpeg","bins":["ffmpeg"],"label":"Install ffmpeg (brew)"}]}} metadata:
{
"openclaw":
{
"emoji": "🎞️",
"requires": { "bins": ["ffmpeg"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "ffmpeg",
"bins": ["ffmpeg"],
"label": "Install ffmpeg (brew)",
},
],
},
}
--- ---
# Video Frames (ffmpeg) # Video Frames (ffmpeg)

View File

@@ -1,7 +1,15 @@
--- ---
name: voice-call name: voice-call
description: Start voice calls via the OpenClaw voice-call plugin. description: Start voice calls via the OpenClaw voice-call plugin.
metadata: {"openclaw":{"emoji":"📞","skillKey":"voice-call","requires":{"config":["plugins.entries.voice-call.enabled"]}}} metadata:
{
"openclaw":
{
"emoji": "📞",
"skillKey": "voice-call",
"requires": { "config": ["plugins.entries.voice-call.enabled"] },
},
}
--- ---
# Voice Call # Voice Call
@@ -20,6 +28,7 @@ openclaw voicecall status --call-id <id>
Use `voice_call` for agent-initiated calls. Use `voice_call` for agent-initiated calls.
Actions: Actions:
- `initiate_call` (message, to?, mode?) - `initiate_call` (message, to?, mode?)
- `continue_call` (callId, message) - `continue_call` (callId, message)
- `speak_to_user` (callId, message) - `speak_to_user` (callId, message)
@@ -27,6 +36,7 @@ Actions:
- `get_status` (callId) - `get_status` (callId)
Notes: Notes:
- Requires the voice-call plugin to be enabled. - Requires the voice-call plugin to be enabled.
- Plugin config lives under `plugins.entries.voice-call.config`. - Plugin config lives under `plugins.entries.voice-call.config`.
- Twilio config: `provider: "twilio"` + `twilio.accountSid/authToken` + `fromNumber`. - Twilio config: `provider: "twilio"` + `twilio.accountSid/authToken` + `fromNumber`.

View File

@@ -2,7 +2,31 @@
name: wacli name: wacli
description: Send WhatsApp messages to other people or search/sync WhatsApp history via the wacli CLI (not for normal user chats). description: Send WhatsApp messages to other people or search/sync WhatsApp history via the wacli CLI (not for normal user chats).
homepage: https://wacli.sh homepage: https://wacli.sh
metadata: {"openclaw":{"emoji":"📱","requires":{"bins":["wacli"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/wacli","bins":["wacli"],"label":"Install wacli (brew)"},{"id":"go","kind":"go","module":"github.com/steipete/wacli/cmd/wacli@latest","bins":["wacli"],"label":"Install wacli (go)"}]}} metadata:
{
"openclaw":
{
"emoji": "📱",
"requires": { "bins": ["wacli"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "steipete/tap/wacli",
"bins": ["wacli"],
"label": "Install wacli (brew)",
},
{
"id": "go",
"kind": "go",
"module": "github.com/steipete/wacli/cmd/wacli@latest",
"bins": ["wacli"],
"label": "Install wacli (go)",
},
],
},
}
--- ---
# wacli # wacli
@@ -12,29 +36,35 @@ Do NOT use `wacli` for normal user chats; OpenClaw routes WhatsApp conversations
If the user is chatting with you on WhatsApp, you should not reach for this tool unless they ask you to contact a third party. If the user is chatting with you on WhatsApp, you should not reach for this tool unless they ask you to contact a third party.
Safety Safety
- Require explicit recipient + message text. - Require explicit recipient + message text.
- Confirm recipient + message before sending. - Confirm recipient + message before sending.
- If anything is ambiguous, ask a clarifying question. - If anything is ambiguous, ask a clarifying question.
Auth + sync Auth + sync
- `wacli auth` (QR login + initial sync) - `wacli auth` (QR login + initial sync)
- `wacli sync --follow` (continuous sync) - `wacli sync --follow` (continuous sync)
- `wacli doctor` - `wacli doctor`
Find chats + messages Find chats + messages
- `wacli chats list --limit 20 --query "name or number"` - `wacli chats list --limit 20 --query "name or number"`
- `wacli messages search "query" --limit 20 --chat <jid>` - `wacli messages search "query" --limit 20 --chat <jid>`
- `wacli messages search "invoice" --after 2025-01-01 --before 2025-12-31` - `wacli messages search "invoice" --after 2025-01-01 --before 2025-12-31`
History backfill History backfill
- `wacli history backfill --chat <jid> --requests 2 --count 50` - `wacli history backfill --chat <jid> --requests 2 --count 50`
Send Send
- Text: `wacli send text --to "+14155551212" --message "Hello! Are you free at 3pm?"` - Text: `wacli send text --to "+14155551212" --message "Hello! Are you free at 3pm?"`
- Group: `wacli send text --to "1234567890-123456789@g.us" --message "Running 5 min late."` - Group: `wacli send text --to "1234567890-123456789@g.us" --message "Running 5 min late."`
- File: `wacli send file --to "+14155551212" --file /path/agenda.pdf --caption "Agenda"` - File: `wacli send file --to "+14155551212" --file /path/agenda.pdf --caption "Agenda"`
Notes Notes
- Store dir: `~/.wacli` (override with `--store`). - Store dir: `~/.wacli` (override with `--store`).
- Use `--json` for machine-readable output when parsing. - Use `--json` for machine-readable output when parsing.
- Backfill requires your phone online; results are best-effort. - Backfill requires your phone online; results are best-effort.

View File

@@ -2,7 +2,7 @@
name: weather name: weather
description: Get current weather and forecasts (no API key required). description: Get current weather and forecasts (no API key required).
homepage: https://wttr.in/:help homepage: https://wttr.in/:help
metadata: {"openclaw":{"emoji":"🌤️","requires":{"bins":["curl"]}}} metadata: { "openclaw": { "emoji": "🌤️", "requires": { "bins": ["curl"] } } }
--- ---
# Weather # Weather
@@ -12,18 +12,21 @@ Two free services, no API keys needed.
## wttr.in (primary) ## wttr.in (primary)
Quick one-liner: Quick one-liner:
```bash ```bash
curl -s "wttr.in/London?format=3" curl -s "wttr.in/London?format=3"
# Output: London: ⛅️ +8°C # Output: London: ⛅️ +8°C
``` ```
Compact format: Compact format:
```bash ```bash
curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w" curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"
# Output: London: ⛅️ +8°C 71% ↙5km/h # Output: London: ⛅️ +8°C 71% ↙5km/h
``` ```
Full forecast: Full forecast:
```bash ```bash
curl -s "wttr.in/London?T" curl -s "wttr.in/London?T"
``` ```
@@ -31,6 +34,7 @@ curl -s "wttr.in/London?T"
Format codes: `%c` condition · `%t` temp · `%h` humidity · `%w` wind · `%l` location · `%m` moon Format codes: `%c` condition · `%t` temp · `%h` humidity · `%w` wind · `%l` location · `%m` moon
Tips: Tips:
- URL-encode spaces: `wttr.in/New+York` - URL-encode spaces: `wttr.in/New+York`
- Airport codes: `wttr.in/JFK` - Airport codes: `wttr.in/JFK`
- Units: `?m` (metric) `?u` (USCS) - Units: `?m` (metric) `?u` (USCS)
@@ -40,6 +44,7 @@ Tips:
## Open-Meteo (fallback, JSON) ## Open-Meteo (fallback, JSON)
Free, no key, good for programmatic use: Free, no key, good for programmatic use:
```bash ```bash
curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12&current_weather=true" curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12&current_weather=true"
``` ```