Routines let your pet fetch data from APIs, parse responses, evaluate conditions, and deliver results—all defined in a simple JSON file. No compilation required.
Open Interactive Routine Builder
Quick Start
- Create a
.jsonfile inside the~/.jacky/routines/directory. - Restart the pet or open Settings and click Save to trigger a config reload.
- Your routine will appear in the right-click context menu under 📋 Rutinas.
The system includes an example_weather.json.disabled in the default routines folder. Rename it to example_weather.json to see a working API-fetching weather routine!
Routine Types
| Type | schedule | How it Runs |
|---|---|---|
| Manual | null or omitted | Triggered by the user via: keyword match, LLM intent, or right-clicking and selecting it in the context menu. |
| Automatic | { "interval": <seconds> } | Runs in the background on a repeating timer. Appears as read-only status in the context menu. |
JSON Structure
A routine is defined by a JSON schema placed under ~/.jacky/routines/:
{
"id": "btc_check",
"title": "Bitcoin Tracker",
"description": "Notifies about cryptocurrency price movements",
"enabled": true,
"schedule": {
"interval": 3600
},
"triggers": ["bitcoin", "btc"],
"variables": {
"threshold": "100000"
},
"internal_variables": {},
"steps": [],
"logic": [],
"actions": {}
}Fields Specification
id: String (Required). Unique identifier for the routine.title: String (Required). Display name in menus and notifications.description: String (Optional). Description of what the routine does.enabled: Boolean (Optional, defaults totrue). Set tofalseto disable the routine without deleting the file.schedule: Object (Optional). Set tonullor omit for manual routines. Set{ "interval": 300 }to run every 5 minutes.triggers: Array of Strings (Optional). Keywords that trigger the routine directly from user input.variables: Object (Optional). Predefined variables that can be overridden dynamically by the user's natural language input.internal_variables: Object (Optional). Immutable variables that cannot be overridden by the LLM.steps: Array of Objects (Optional). Sequential workflow steps.logic: Array of Objects (Optional). Conditional rules to select the final action.actions: Object (Optional). Named final actions (e.g. text for the pet to say, notifications, or logs).
Variables & Placeholders
Variables are the glue between steps. They live in a shared context dictionary that every step can read from and write to. Use {{variable_name}} inside string attributes (URLs, headers, query params, body values, actions, etc.) to interpolate dynamic values at runtime.
Built-in Variables
These are always available in the context of any routine:
| Variable | Value |
|---|---|
{{timestamp}} | Current time in UTC ISO format (2024-01-15T14:30:00Z). |
{{timestamp_plus_<N>m}} | Now + N minutes, RFC3339 UTC (e.g. {{timestamp_plus_5m}} → 2024-01-15T14:35:00Z). |
{{timestamp_minus_<N>m}} | Now − N minutes, RFC3339 UTC (e.g. {{timestamp_minus_10m}} → 2024-01-15T14:20:00Z). |
{{routine_id}} | The routine's id |
{{routine_title}} | The routine's title |
{{state.<key>}} | Value of <key> in the routine's persistent state (returns empty string if missing). |
Predefined Variables
Define default variables in the top-level "variables" object:
"variables": {
"city": "CDMX",
"api_key": "abc123"
}These are loaded into context before any steps run and can be referenced as {{city}} or {{api_key}}.
Dynamic Overrides (User Input)
Every variable declared in "variables" acts as an overridable parameter. If a user triggers a routine using natural language (e.g., "tell me the weather at Paris"), the LLM automatically extracts the parameter value ("city": "Paris") and overrides the default value.
If the user does not specify a value, the default is used.
Internal & Immutable Variables
For variables that must not be exposed to the LLM or overridden by the user (like private repository endpoints, OAuth constants, or static usernames), use "internal_variables":
"internal_variables": {
"owner": "jackyclub",
"repo": "myrepo"
}Steps Reference
Steps execute sequentially. If a step fails, the execution halts safely.
1. HTTP Request (type: "request")
Fetches data from external API endpoints:
{
"id": "fetch_weather",
"type": "request",
"method": "GET",
"url": "https://api.weatherapi.com/v1/current.json?key={{api_key}}&q={{city}}",
"headers": {
"Accept": "application/json"
},
"timeout": 10,
"output_var": "raw_response"
}| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique step identifier (for logging). |
type | string | — | Must be "request". |
method | string | "GET" | HTTP method: GET, POST, PUT, DELETE, etc. |
url | string | — | Full URL. Supports {{variables}}. |
headers | object | {} | HTTP headers. Values support {{variables}}. |
params | object | {} | URL query parameters. Appended to the URL. |
body | object | null | JSON body (for POST/PUT/DELETE). Sent as Content-Type: application/json. |
timeout | integer | 10 | Request timeout in seconds. |
auth | object | null | OAuth 2.0 authentication block. See OAuth 2.0 section. |
output_var | string | "" | Variable name to store the raw response body text. |
2. Parse (type: "parse")
Extracts values using dot-paths (JSON), tag paths (XML), or regular expressions:
{
"id": "parse_temp",
"type": "parse",
"input": "{{raw_response}}",
"parser": "json",
"query": "current.temp_c",
"output_var": "temp_c"
}| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique step identifier. |
type | string | — | Must be "parse". |
input | string | — | The text to parse. Usually {{some_var}} from a previous step. |
parser | string | — | Parser type: "json", "xml", or "regex". |
query | string | — | Parser-specific query. |
output_var | string | "" | Variable name to store the extracted value. |
JSON Parser
Uses dot-path navigation. Array indices are integers (e.g. data.items.0.name).
XML Parser
Uses tag path search compatible with Python's ElementTree.find() (e.g. channel/item/title).
Regex Parser
Uses regular expression capture groups. Returns the first captured group or the full match.
Backslashes in JSON must be escaped (e.g., use \\d instead of \d).
3. Script (type: "script")
Runs a sandboxed QuickJS script to perform logic, parsing, or data transformations:
{
"id": "compute",
"type": "script",
"code": "const val = Number(temp_c); val * 1.8 + 32;",
"output_var": "temp_f"
}| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique step identifier. |
type | string | — | Must be "script". |
code | string | "" | Inline JavaScript source. Either code or code_file is required. |
code_file | string | "" | Path to a .js file, relative to the routine .json directory. |
output_var | string | "" | Variable name to store the script's return value. |
Sandbox Limits (Hardcoded)
- Timeout: 500ms wall-clock limit.
- Memory: 32MB heap limit.
- Stack: 256KB stack limit.
- No I/O: No network (
fetch,XMLHttpRequest) or filesystem access.
Script Environment & Debugging
- All context variables are loaded as JS globals.
- Standard ES built-ins are available (
Math,JSON,Date, etc.). - The return value of the last statement is captured.
console.log,console.warn, etc. are captured in the routines debug log.
Scripts use quickjs to run.
4. Set State (type: "set_state")
Persists values across runs in a localized state database:
{
"id": "save_state",
"type": "set_state",
"updates": {
"last_check": "{{timestamp}}",
"last_temp": "{{temp_c}}"
}
}| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique step identifier. |
type | string | — | Must be "set_state". |
key | string | "" | Single key to update (ignored if updates is defined). |
value | string | "" | Value for the single key. |
updates | object | {} | Multi-key update map. |
5. Filesystem (type: "filesystem")
Used to search or list file contents locally:
{
"id": "list_files",
"type": "filesystem",
"query": "list_folder",
"params": {
"folder": "C:/Users/User/Desktop/Docs"
},
"output_var": "file_list"
}Supported queries:
list_desktop: Lists files on the user's desktop.list_folder: Lists files in a folder path defined underparams.folder(supports absolute paths and variables).
6. Home Assistant Steps
Routines can interact with Home Assistant. See the Home Assistant Integration section below for details.
ha_resolve_entity: Resolves a natural-language query to an entity ID.ha_call_service: Calls a smart-home service.
Authenticated Requests (OAuth 2.0)
Any request step can attach an auth block to automatically manage OAuth 2.0 authorization, token storage, and background refreshes.
The "connection" name specified inside the auth block (e.g., "github" or "google_calendar") must exactly match the connection name of the integration stored and configured in the application under Settings → Integrations.
{
"id": "fetch_profile",
"type": "request",
"url": "https://api.github.com/user",
"auth": {
"type": "oauth2",
"connection": "github",
"header": "Authorization",
"scheme": "Bearer"
},
"output_var": "profile"
}Connections Configuration
- In the app, go to Settings → Integrations.
- Add a connection using a preset (Google, GitHub, Microsoft, Spotify, Twitch) or Custom.
- Jacky launches a temporary server to securely capture the authorization token from the browser.
- Tokens are stored encrypted-at-rest using your OS keychain/credential manager.
Persistent State
Every routine is isolated and can maintain persistent state in <config_dir>/routine_state.json.
- Reading: Access state values using dotted interpolation:
{{state.last_temp}}. - Writing: Use the
set_statestep type to update values.
Logic
The logic block is a list of conditional rules evaluated in order. The first matching rule selects the action to run.
"logic": [
{
"if": { "var": "temp_c", "op": ">", "val": 35 },
"then": "action_extreme",
"else": null
},
{
"if": {
"and": [
{ "var": "temp_c", "op": "<", "val": 15 },
{ "var": "humidity", "op": ">", "val": 80 }
]
},
"then": "action_cold_humid",
"else": "default"
}
]Operators & Nested Rules
- Supported operators:
>,<,>=,<=,==,!=,contains. - Numeric comparisons automatically convert string inputs to numbers if possible.
- Nest checks using
"and"or"or"lists. - If
"logic"is omitted or no rules match, the action named"default"is run. If there is no"default"action, no action executes.
Actions
Actions determine what the pet does with the routine's output context.
"actions": {
"action_extreme": {
"type": "say",
"llm": "It is extremely hot at {{temp_c}}°C! Warn the user playfully.",
"nollm": "🥵 Whew! It is {{temp_c}}°C outside!"
},
"action_log": {
"type": "log",
"message": "Routine ran at {{timestamp}}"
}
}Action Types
1. Say (type: "say")
The pet speaks.
llm: A prompt sent to the LLM (if enabled) to generate a natural, context-aware speech bubble.nollm: Plain text used if the LLM is disabled or fails.- Multilingual support: Text fields can be structured as language dictionaries:
"nollm": { "en": "Hello", "es": "Hola" }
2. Notification (type: "notification")
Triggers an OS system tray notification.
message: The notification message text.
3. Log (type: "log")
Silently writes a message into the application's routine log.
message: The log message text.
4. Organize (type: "organize")
Triggers a file organization confirmation flow.
confirm_msg: Speech bubble text the pet says while showing the files.
Triggering Manual Routines
Manual routines can be triggered in three ways:
- Context Menu: Right-click the pet, expand 📋 Rutinas, and click the manual routine.
- Keyword Triggers: If the user input matches a word in the
"triggers"array, the routine runs immediately. - LLM Intent: Paraphrased user commands are analyzed by the LLM to classify and map them to the correct routine.
Automatic Routines
Set a schedule interval (in seconds) to run routines automatically:
"schedule": {
"interval": 1800
}- Background timers pause automatically during Gamer/Silent Mode and resume after.
- Saving settings automatically reloads all routine configurations and restarts timers.
Home Assistant Integration
Jacky can drive a local Home Assistant (HA) instance to control lights, switches, and other entities.
Setup
- In the app, open Settings → Integrations → Home Assistant.
- Input your HA LAN URL and a Long-lived Access Token.
- In Settings → Permissions, ensure Control smart home (
allow_smart_home) is toggled on.
Steps Reference (HA)
1. ha_resolve_entity
Resolves a natural-language query to an entity ID.
{
"id": "find_light",
"type": "ha_resolve_entity",
"ha_query": "{{user_query}}",
"ha_domain_filter": "light,switch",
"output_var": "target_entity"
}- Returns the resolved entity ID or an empty string if nothing matches.
- Uses local fuzzy matching and drops back to LLM selection when local search fails.
2. ha_call_service
Calls an HA service.
{
"id": "toggle_device",
"type": "ha_call_service",
"ha_entity_var": "target_entity",
"ha_service": "toggle",
"ha_data": {
"transition": 2
}
}
------ha_entity_var: Variable containing the target entity ID (e.g.,target_entity).ha_entity_id: Literal entity ID fallback (e.g.,light.living_room).ha_service: Service name (e.g.,turn_on,turn_off,toggle).ha_domain: Service domain (inferred from entity prefix if omitted).ha_data: Service data JSON payload.
Complete Example
A routine that fetches the price of Bitcoin, branches depending on whether it's above or below $100k, and reacts:
{
"id": "btc_price",
"title": "Bitcoin Price",
"description": "Checks the current BTC price from CoinGecko",
"schedule": null,
"triggers": ["bitcoin", "btc", "crypto"],
"enabled": true,
"steps": [
{
"id": "fetch_price",
"type": "request",
"method": "GET",
"url": "https://api.coingecko.com/api/v3/simple/price",
"params": {
"ids": "bitcoin",
"vs_currencies": "usd"
},
"timeout": 10,
"output_var": "raw_price"
},
{
"id": "parse_price",
"type": "parse",
"input": "{{raw_price}}",
"parser": "json",
"query": "bitcoin.usd",
"output_var": "btc_usd"
}
],
"logic": [
{
"if": { "var": "btc_usd", "op": ">", "val": 100000 },
"then": "moon",
"else": "normal"
}
],
"actions": {
"moon": {
"type": "say",
"llm": "Bitcoin is at ${{btc_usd}} USD! It's above 100k! React excited!",
"nollm": "🚀 BTC: ${{btc_usd}} USD — To the moon!"
},
"normal": {
"type": "say",
"llm": "Bitcoin is currently at ${{btc_usd}} USD. Make a brief comment.",
"nollm": "₿ BTC: ${{btc_usd}} USD"
}
},
"variables": {}
}Tips & Best Practices
- Start simple: A single
requeststep and a"default"action are enough to get started. - Test APIs first: Try URLs in browser or Postman/curl before embedding them in steps.
- Define
output_var: Any step output you want to reference later must be stored in a variable. - Duplicate IDs: If two routine files share an
id, only the first one loaded (alphabetically by filename) will be registered.