kinenix-worker 0.2.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. kinenix_worker-0.2.0b1/LICENSE +21 -0
  2. kinenix_worker-0.2.0b1/PKG-INFO +236 -0
  3. kinenix_worker-0.2.0b1/README.md +207 -0
  4. kinenix_worker-0.2.0b1/kinenix_worker/__init__.py +5 -0
  5. kinenix_worker-0.2.0b1/kinenix_worker/cli.py +186 -0
  6. kinenix_worker-0.2.0b1/kinenix_worker/display.py +89 -0
  7. kinenix_worker-0.2.0b1/kinenix_worker/hub_client.py +163 -0
  8. kinenix_worker-0.2.0b1/kinenix_worker/runner.py +158 -0
  9. kinenix_worker-0.2.0b1/kinenix_worker/triggers/__init__.py +9 -0
  10. kinenix_worker-0.2.0b1/kinenix_worker/triggers/cron.py +75 -0
  11. kinenix_worker-0.2.0b1/kinenix_worker/triggers/manager.py +101 -0
  12. kinenix_worker-0.2.0b1/kinenix_worker/triggers/scheduler.py +105 -0
  13. kinenix_worker-0.2.0b1/kinenix_worker/triggers/watcher.py +125 -0
  14. kinenix_worker-0.2.0b1/kinenix_worker.egg-info/PKG-INFO +236 -0
  15. kinenix_worker-0.2.0b1/kinenix_worker.egg-info/SOURCES.txt +25 -0
  16. kinenix_worker-0.2.0b1/kinenix_worker.egg-info/dependency_links.txt +1 -0
  17. kinenix_worker-0.2.0b1/kinenix_worker.egg-info/entry_points.txt +2 -0
  18. kinenix_worker-0.2.0b1/kinenix_worker.egg-info/requires.txt +7 -0
  19. kinenix_worker-0.2.0b1/kinenix_worker.egg-info/top_level.txt +1 -0
  20. kinenix_worker-0.2.0b1/pyproject.toml +49 -0
  21. kinenix_worker-0.2.0b1/setup.cfg +4 -0
  22. kinenix_worker-0.2.0b1/tests/test_cron.py +65 -0
  23. kinenix_worker-0.2.0b1/tests/test_display.py +53 -0
  24. kinenix_worker-0.2.0b1/tests/test_hub_client.py +109 -0
  25. kinenix_worker-0.2.0b1/tests/test_runner_flow_names.py +29 -0
  26. kinenix_worker-0.2.0b1/tests/test_triggers.py +115 -0
  27. kinenix_worker-0.2.0b1/tests/test_worker.py +46 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 arttopix / Kinenix
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: kinenix-worker
3
+ Version: 0.2.0b1
4
+ Summary: Kinenix Worker: runs Kinenix flows unattended on schedules, cron, or file triggers, and reports to the Kinenix Hub
5
+ Author: arttopix
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/arttopix/Kinenix
8
+ Project-URL: Documentation, https://github.com/arttopix/Kinenix/blob/main/kinenix-worker/README.md
9
+ Project-URL: Issues, https://github.com/arttopix/Kinenix/issues
10
+ Keywords: rpa,automation,raspberry-pi,scheduler,worker
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Office/Business
18
+ Classifier: Topic :: System :: Distributed Computing
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: kinenix>=0.2.0b1
23
+ Requires-Dist: psutil>=5.9.0
24
+ Requires-Dist: requests>=2.28.0
25
+ Requires-Dist: rich>=13.0.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # kinenix-worker
31
+
32
+ The unattended robot worker daemon and edge execution engine for **[Kinenix](https://github.com/arttopix/Kinenix)**, an open-source, local AI-native RPA framework in Python.
33
+
34
+ ## Install from PyPI
35
+
36
+ ```bash
37
+ pip install "kinenix[worker]"
38
+ kinenix install-browsers # Playwright Chromium, needed for web actions
39
+ kinenix-worker info
40
+ ```
41
+
42
+ Requires Python 3.10 or newer. To report to a Kinenix Hub, set `KINENIX_HUB_URL` and `KINENIX_HUB_API_KEY` (section 3D). The Raspberry Pi quickstart below installs from the repository instead and also sets up a systemd service.
43
+
44
+ ---
45
+
46
+ ## 1. Overview
47
+
48
+ `kinenix-worker` is designed to run unattended automation workloads on edge hardware, dedicated worker machines, virtual machines, and low-power devices such as **Raspberry Pi 4 Model B**.
49
+
50
+ ### Key Capabilities
51
+ - **Lightweight Execution:** Minimal overhead with direct integration into `kinenix-core`.
52
+ - **Sandbox Workspace Isolation:** Executes project bundles inside disposable, per-job sandbox environments.
53
+ - **Hardware Telemetry:** Gathers CPU, memory, architecture, and platform metrics at runtime.
54
+ - **Structured JSON Auditing:** Stores hierarchical execution traces and error telemetry locally.
55
+
56
+ ---
57
+
58
+ ## 2. Raspberry Pi 4 Model B (ARM64) Quickstart
59
+
60
+ ### Prerequisites
61
+ - **Hardware:** Raspberry Pi 4 Model B (Rev 1.4 or compatible) with 2GB, 4GB, or 8GB RAM.
62
+ - **Operating System:** Raspberry Pi OS (64-bit Debian bookworm / bullseye) or Ubuntu Server ARM64.
63
+ - **Network:** Internet access for initial setup and downloading benchmark web pages.
64
+
65
+ ### Automated Setup
66
+ Clone the repository and run the setup script:
67
+
68
+ ```bash
69
+ # 1. Clone repository onto Raspberry Pi
70
+ git clone -b dev https://github.com/arttopix/Kinenix.git kinenix
71
+ cd kinenix
72
+
73
+ # 2. Run the setup script (it is already executable; do not chmod it, or git pull will see a local change)
74
+ ./kinenix-worker/scripts/setup_rpi.sh
75
+ ```
76
+
77
+ The script automatically:
78
+ 1. Installs required Linux shared libraries for Chromium on aarch64.
79
+ 2. Creates a Python 3.10+ virtual environment (`.venv`).
80
+ 3. Installs `kinenix-core` and `kinenix-worker` in editable mode.
81
+ 4. Installs the ARM64 Playwright Chromium browser.
82
+ 5. Verifies the installation with `kinenix-worker info`.
83
+ 6. Installs and starts the `kinenix-worker` systemd service (see [section 3E](#e-run-as-a-systemd-service)). Pass `--no-service` to skip it.
84
+
85
+ ---
86
+
87
+ ## 3. Running Workflows on Raspberry Pi
88
+
89
+ ### A. Activate Environment
90
+ ```bash
91
+ source .venv/bin/activate
92
+ ```
93
+
94
+ ### B. Verify System Information
95
+ ```bash
96
+ kinenix-worker info
97
+ ```
98
+
99
+ Expected output on Raspberry Pi 4:
100
+ ```text
101
+ Kinenix Worker System Information:
102
+ -------------------------------
103
+ os: Linux
104
+ os_release: 6.6.x+rpt-rpi-v8
105
+ machine: aarch64
106
+ processor: aarch64
107
+ python_version: 3.11.x
108
+ cpu_count: 4
109
+ memory_total_mb: 3884.2
110
+ memory_available_mb: 3200.1
111
+ memory_used_percent: 17.6
112
+ worker_id: raspberrypi
113
+ hub_url: (not set)
114
+ hub_api_key: (not set)
115
+ ```
116
+
117
+ ### C. Run RPA Challenge Example Flow
118
+ ```bash
119
+ kinenix-worker run flows/examples/rpachallenge/flow.json
120
+ ```
121
+
122
+ To run inside an isolated sandbox directory:
123
+ ```bash
124
+ kinenix-worker run flows/examples/rpachallenge/flow.json --sandbox
125
+ ```
126
+
127
+ ### D. Connect to the Hub
128
+
129
+ The worker reports to a Kinenix Hub when these environment variables are set (see the [Hub guide](https://github.com/arttopix/Kinenix/blob/main/docs/hub.md) for the server side):
130
+
131
+ | Variable | Default | Purpose |
132
+ | :--- | :--- | :--- |
133
+ | `KINENIX_HUB_URL` | *(unset: no reporting)* | Hub base URL, for example `http://192.168.1.132:8080` |
134
+ | `KINENIX_HUB_API_KEY` | *(unset)* | The Hub's worker key; the same variable holds the same value on the Hub |
135
+ | `KINENIX_WORKER_ID` | host name | Name shown on the dashboard and attached to every execution |
136
+ | `KINENIX_HEARTBEAT_INTERVAL` | `30` | Seconds between heartbeats while a worker command runs |
137
+
138
+ ```bash
139
+ export KINENIX_HUB_URL=http://192.168.1.132:8080
140
+ export KINENIX_HUB_API_KEY="<key>"
141
+ export KINENIX_WORKER_ID=rpi4-01
142
+
143
+ kinenix-worker ping # checks reachability and the API key; exit code 1 on failure
144
+ kinenix-worker run flows/examples/rpachallenge
145
+ ```
146
+
147
+ With the URL set:
148
+
149
+ - Every run sends its execution log to the Hub. `--log-dir` is not needed; it only controls whether a local log file is also written.
150
+ - Every run reports the worker as busy while the flow runs and online when it finishes.
151
+ - Every command that runs flows (`run`, `watch`, `schedule`, `daemon`) also sends a heartbeat every `KINENIX_HEARTBEAT_INTERVAL` seconds, including during long flows. The dashboard shows the worker as offline when heartbeats stop.
152
+ - If the Hub is unreachable, flows still run. The worker logs one warning, then logs again when the connection recovers.
153
+
154
+ ### E. Run as a systemd Service
155
+
156
+ The service keeps `kinenix-worker daemon` running in the background, starts it at boot, and restarts it within 10 seconds if it stops. It runs as the user who installed it, from the repository directory, so relative flow paths such as `flows/examples/rpachallenge` work. While it runs, the worker sends heartbeats and shows as online in the Hub.
157
+
158
+ `setup_rpi.sh` installs it. To install or update it on an existing setup, run as your normal user (not root):
159
+
160
+ ```bash
161
+ ./kinenix-worker/scripts/install_service.sh
162
+ ```
163
+
164
+ It reads two files in `~/.kinenix/`, creating them if they do not exist (existing files are never overwritten):
165
+
166
+ | File | Contents |
167
+ | :--- | :--- |
168
+ | `worker.env` | `KINENIX_HUB_URL`, `KINENIX_HUB_API_KEY`, `KINENIX_WORKER_ID` as `export` lines; owner-readable only. Also put the secrets your flows read here, for example `GMAIL_USER` and `GMAIL_APP_PASSWORD` for `email.send`: the service does not see variables set in your shell. Add `source ~/.kinenix/worker.env` to `~/.bashrc` to use the same values in your shell. |
169
+ | `triggers.json` | Triggers for the daemon. The default `{"triggers": []}` runs no flows and only sends heartbeats. |
170
+
171
+ **Your own flows** belong in your flows folder, `~/kinenix-flows`, not in this repository (see [Where your flows live](https://github.com/arttopix/Kinenix/blob/main/docs/quickstart.md#2-where-your-flows-live)). Keep them in a private git repository and clone it on the Pi:
172
+
173
+ ```bash
174
+ git clone git@github.com:<you>/kinenix-flows.git ~/kinenix-flows # your private repository
175
+ cd ~/kinenix-flows && git pull # later, to update the flows
176
+ ```
177
+
178
+ A trigger can then name a flow by its folder name; the worker looks it up in the flows folder (or in `KINENIX_FLOWS_DIR`). Paths still work too, relative to this repository.
179
+
180
+ Example `triggers.json`:
181
+
182
+ ```json
183
+ {
184
+ "triggers": [
185
+ { "type": "scheduler", "flow": "get_stock_data", "cron": "0 8 * * 1-5" },
186
+ { "type": "scheduler", "flow": "flows/examples/bot_fx_rate", "cron": "0 8 1 * *" },
187
+ { "type": "file_watcher", "flow": "invoice_check", "watch_dir": "/home/pi/inbox", "pattern": "*.xlsx" }
188
+ ]
189
+ }
190
+ ```
191
+
192
+ A scheduler runs either every `interval_seconds` or, when `cron` is set, at the minutes matching a five-field cron expression in the Pi's local time (`minute hour day-of-month month day-of-week`, Sunday is 0 or 7):
193
+
194
+ | `cron` | Runs |
195
+ | :--- | :--- |
196
+ | `0 8 1 * *` | 08:00 on the 1st of every month |
197
+ | `30 7 * * 1-5` | 07:30 Monday to Friday |
198
+ | `*/15 * * * *` | Every 15 minutes |
199
+ | `0 9 1,15 * *` | 09:00 on the 1st and 15th |
200
+
201
+ An invalid expression stops the daemon at startup with the field that is wrong. Check the Pi's time zone with `timedatectl` (set it with `sudo timedatectl set-timezone Asia/Bangkok`).
202
+
203
+ | Task | Command |
204
+ | :--- | :--- |
205
+ | Apply changes to `worker.env` or `triggers.json` | `sudo systemctl restart kinenix-worker` |
206
+ | Status | `systemctl status kinenix-worker` |
207
+ | Follow logs | `journalctl -u kinenix-worker -f` |
208
+ | Stop until next boot | `sudo systemctl stop kinenix-worker` |
209
+ | Remove the service | `./kinenix-worker/scripts/install_service.sh --uninstall` |
210
+
211
+ Jobs sent from the Hub are not supported yet; each worker runs the triggers in its own `triggers.json`.
212
+
213
+ ---
214
+
215
+ ## 4. CLI Reference
216
+
217
+ ```text
218
+ usage: kinenix-worker [-h] [--version] {info,ping,run,watch,schedule,daemon} ...
219
+
220
+ positional arguments:
221
+ info Display worker machine hardware, architecture, and runtime stats
222
+ ping Check the connection and API key to the Hub (KINENIX_HUB_URL)
223
+ run Execute a flow or project bundle on this worker
224
+ watch Watch a directory and automatically trigger a flow when new files appear
225
+ schedule Execute a flow on an interval (--interval) or a cron expression (--cron "0 8 1 * *")
226
+ daemon Run multi-trigger daemon using a configuration file
227
+
228
+ options:
229
+ --version, -v
230
+ ```
231
+
232
+ ### `kinenix-worker run` Options
233
+ - `flow_path`: Path to `flow.json` or project bundle directory.
234
+ - `--sandbox`: Execute inside an isolated temporary sandbox (`~/.kinenix/workspaces/<job_id>`).
235
+ - `--vars`: JSON string of variables to override.
236
+ - `--log-dir`: Also write a local JSON execution log to this directory. Telemetry to the Hub does not depend on it.
@@ -0,0 +1,207 @@
1
+ # kinenix-worker
2
+
3
+ The unattended robot worker daemon and edge execution engine for **[Kinenix](https://github.com/arttopix/Kinenix)**, an open-source, local AI-native RPA framework in Python.
4
+
5
+ ## Install from PyPI
6
+
7
+ ```bash
8
+ pip install "kinenix[worker]"
9
+ kinenix install-browsers # Playwright Chromium, needed for web actions
10
+ kinenix-worker info
11
+ ```
12
+
13
+ Requires Python 3.10 or newer. To report to a Kinenix Hub, set `KINENIX_HUB_URL` and `KINENIX_HUB_API_KEY` (section 3D). The Raspberry Pi quickstart below installs from the repository instead and also sets up a systemd service.
14
+
15
+ ---
16
+
17
+ ## 1. Overview
18
+
19
+ `kinenix-worker` is designed to run unattended automation workloads on edge hardware, dedicated worker machines, virtual machines, and low-power devices such as **Raspberry Pi 4 Model B**.
20
+
21
+ ### Key Capabilities
22
+ - **Lightweight Execution:** Minimal overhead with direct integration into `kinenix-core`.
23
+ - **Sandbox Workspace Isolation:** Executes project bundles inside disposable, per-job sandbox environments.
24
+ - **Hardware Telemetry:** Gathers CPU, memory, architecture, and platform metrics at runtime.
25
+ - **Structured JSON Auditing:** Stores hierarchical execution traces and error telemetry locally.
26
+
27
+ ---
28
+
29
+ ## 2. Raspberry Pi 4 Model B (ARM64) Quickstart
30
+
31
+ ### Prerequisites
32
+ - **Hardware:** Raspberry Pi 4 Model B (Rev 1.4 or compatible) with 2GB, 4GB, or 8GB RAM.
33
+ - **Operating System:** Raspberry Pi OS (64-bit Debian bookworm / bullseye) or Ubuntu Server ARM64.
34
+ - **Network:** Internet access for initial setup and downloading benchmark web pages.
35
+
36
+ ### Automated Setup
37
+ Clone the repository and run the setup script:
38
+
39
+ ```bash
40
+ # 1. Clone repository onto Raspberry Pi
41
+ git clone -b dev https://github.com/arttopix/Kinenix.git kinenix
42
+ cd kinenix
43
+
44
+ # 2. Run the setup script (it is already executable; do not chmod it, or git pull will see a local change)
45
+ ./kinenix-worker/scripts/setup_rpi.sh
46
+ ```
47
+
48
+ The script automatically:
49
+ 1. Installs required Linux shared libraries for Chromium on aarch64.
50
+ 2. Creates a Python 3.10+ virtual environment (`.venv`).
51
+ 3. Installs `kinenix-core` and `kinenix-worker` in editable mode.
52
+ 4. Installs the ARM64 Playwright Chromium browser.
53
+ 5. Verifies the installation with `kinenix-worker info`.
54
+ 6. Installs and starts the `kinenix-worker` systemd service (see [section 3E](#e-run-as-a-systemd-service)). Pass `--no-service` to skip it.
55
+
56
+ ---
57
+
58
+ ## 3. Running Workflows on Raspberry Pi
59
+
60
+ ### A. Activate Environment
61
+ ```bash
62
+ source .venv/bin/activate
63
+ ```
64
+
65
+ ### B. Verify System Information
66
+ ```bash
67
+ kinenix-worker info
68
+ ```
69
+
70
+ Expected output on Raspberry Pi 4:
71
+ ```text
72
+ Kinenix Worker System Information:
73
+ -------------------------------
74
+ os: Linux
75
+ os_release: 6.6.x+rpt-rpi-v8
76
+ machine: aarch64
77
+ processor: aarch64
78
+ python_version: 3.11.x
79
+ cpu_count: 4
80
+ memory_total_mb: 3884.2
81
+ memory_available_mb: 3200.1
82
+ memory_used_percent: 17.6
83
+ worker_id: raspberrypi
84
+ hub_url: (not set)
85
+ hub_api_key: (not set)
86
+ ```
87
+
88
+ ### C. Run RPA Challenge Example Flow
89
+ ```bash
90
+ kinenix-worker run flows/examples/rpachallenge/flow.json
91
+ ```
92
+
93
+ To run inside an isolated sandbox directory:
94
+ ```bash
95
+ kinenix-worker run flows/examples/rpachallenge/flow.json --sandbox
96
+ ```
97
+
98
+ ### D. Connect to the Hub
99
+
100
+ The worker reports to a Kinenix Hub when these environment variables are set (see the [Hub guide](https://github.com/arttopix/Kinenix/blob/main/docs/hub.md) for the server side):
101
+
102
+ | Variable | Default | Purpose |
103
+ | :--- | :--- | :--- |
104
+ | `KINENIX_HUB_URL` | *(unset: no reporting)* | Hub base URL, for example `http://192.168.1.132:8080` |
105
+ | `KINENIX_HUB_API_KEY` | *(unset)* | The Hub's worker key; the same variable holds the same value on the Hub |
106
+ | `KINENIX_WORKER_ID` | host name | Name shown on the dashboard and attached to every execution |
107
+ | `KINENIX_HEARTBEAT_INTERVAL` | `30` | Seconds between heartbeats while a worker command runs |
108
+
109
+ ```bash
110
+ export KINENIX_HUB_URL=http://192.168.1.132:8080
111
+ export KINENIX_HUB_API_KEY="<key>"
112
+ export KINENIX_WORKER_ID=rpi4-01
113
+
114
+ kinenix-worker ping # checks reachability and the API key; exit code 1 on failure
115
+ kinenix-worker run flows/examples/rpachallenge
116
+ ```
117
+
118
+ With the URL set:
119
+
120
+ - Every run sends its execution log to the Hub. `--log-dir` is not needed; it only controls whether a local log file is also written.
121
+ - Every run reports the worker as busy while the flow runs and online when it finishes.
122
+ - Every command that runs flows (`run`, `watch`, `schedule`, `daemon`) also sends a heartbeat every `KINENIX_HEARTBEAT_INTERVAL` seconds, including during long flows. The dashboard shows the worker as offline when heartbeats stop.
123
+ - If the Hub is unreachable, flows still run. The worker logs one warning, then logs again when the connection recovers.
124
+
125
+ ### E. Run as a systemd Service
126
+
127
+ The service keeps `kinenix-worker daemon` running in the background, starts it at boot, and restarts it within 10 seconds if it stops. It runs as the user who installed it, from the repository directory, so relative flow paths such as `flows/examples/rpachallenge` work. While it runs, the worker sends heartbeats and shows as online in the Hub.
128
+
129
+ `setup_rpi.sh` installs it. To install or update it on an existing setup, run as your normal user (not root):
130
+
131
+ ```bash
132
+ ./kinenix-worker/scripts/install_service.sh
133
+ ```
134
+
135
+ It reads two files in `~/.kinenix/`, creating them if they do not exist (existing files are never overwritten):
136
+
137
+ | File | Contents |
138
+ | :--- | :--- |
139
+ | `worker.env` | `KINENIX_HUB_URL`, `KINENIX_HUB_API_KEY`, `KINENIX_WORKER_ID` as `export` lines; owner-readable only. Also put the secrets your flows read here, for example `GMAIL_USER` and `GMAIL_APP_PASSWORD` for `email.send`: the service does not see variables set in your shell. Add `source ~/.kinenix/worker.env` to `~/.bashrc` to use the same values in your shell. |
140
+ | `triggers.json` | Triggers for the daemon. The default `{"triggers": []}` runs no flows and only sends heartbeats. |
141
+
142
+ **Your own flows** belong in your flows folder, `~/kinenix-flows`, not in this repository (see [Where your flows live](https://github.com/arttopix/Kinenix/blob/main/docs/quickstart.md#2-where-your-flows-live)). Keep them in a private git repository and clone it on the Pi:
143
+
144
+ ```bash
145
+ git clone git@github.com:<you>/kinenix-flows.git ~/kinenix-flows # your private repository
146
+ cd ~/kinenix-flows && git pull # later, to update the flows
147
+ ```
148
+
149
+ A trigger can then name a flow by its folder name; the worker looks it up in the flows folder (or in `KINENIX_FLOWS_DIR`). Paths still work too, relative to this repository.
150
+
151
+ Example `triggers.json`:
152
+
153
+ ```json
154
+ {
155
+ "triggers": [
156
+ { "type": "scheduler", "flow": "get_stock_data", "cron": "0 8 * * 1-5" },
157
+ { "type": "scheduler", "flow": "flows/examples/bot_fx_rate", "cron": "0 8 1 * *" },
158
+ { "type": "file_watcher", "flow": "invoice_check", "watch_dir": "/home/pi/inbox", "pattern": "*.xlsx" }
159
+ ]
160
+ }
161
+ ```
162
+
163
+ A scheduler runs either every `interval_seconds` or, when `cron` is set, at the minutes matching a five-field cron expression in the Pi's local time (`minute hour day-of-month month day-of-week`, Sunday is 0 or 7):
164
+
165
+ | `cron` | Runs |
166
+ | :--- | :--- |
167
+ | `0 8 1 * *` | 08:00 on the 1st of every month |
168
+ | `30 7 * * 1-5` | 07:30 Monday to Friday |
169
+ | `*/15 * * * *` | Every 15 minutes |
170
+ | `0 9 1,15 * *` | 09:00 on the 1st and 15th |
171
+
172
+ An invalid expression stops the daemon at startup with the field that is wrong. Check the Pi's time zone with `timedatectl` (set it with `sudo timedatectl set-timezone Asia/Bangkok`).
173
+
174
+ | Task | Command |
175
+ | :--- | :--- |
176
+ | Apply changes to `worker.env` or `triggers.json` | `sudo systemctl restart kinenix-worker` |
177
+ | Status | `systemctl status kinenix-worker` |
178
+ | Follow logs | `journalctl -u kinenix-worker -f` |
179
+ | Stop until next boot | `sudo systemctl stop kinenix-worker` |
180
+ | Remove the service | `./kinenix-worker/scripts/install_service.sh --uninstall` |
181
+
182
+ Jobs sent from the Hub are not supported yet; each worker runs the triggers in its own `triggers.json`.
183
+
184
+ ---
185
+
186
+ ## 4. CLI Reference
187
+
188
+ ```text
189
+ usage: kinenix-worker [-h] [--version] {info,ping,run,watch,schedule,daemon} ...
190
+
191
+ positional arguments:
192
+ info Display worker machine hardware, architecture, and runtime stats
193
+ ping Check the connection and API key to the Hub (KINENIX_HUB_URL)
194
+ run Execute a flow or project bundle on this worker
195
+ watch Watch a directory and automatically trigger a flow when new files appear
196
+ schedule Execute a flow on an interval (--interval) or a cron expression (--cron "0 8 1 * *")
197
+ daemon Run multi-trigger daemon using a configuration file
198
+
199
+ options:
200
+ --version, -v
201
+ ```
202
+
203
+ ### `kinenix-worker run` Options
204
+ - `flow_path`: Path to `flow.json` or project bundle directory.
205
+ - `--sandbox`: Execute inside an isolated temporary sandbox (`~/.kinenix/workspaces/<job_id>`).
206
+ - `--vars`: JSON string of variables to override.
207
+ - `--log-dir`: Also write a local JSON execution log to this directory. Telemetry to the Hub does not depend on it.
@@ -0,0 +1,5 @@
1
+ """
2
+ Kinenix Worker: Unattended Robot Daemon and Edge Execution Engine.
3
+ """
4
+
5
+ __version__ = "0.2.0b1"
@@ -0,0 +1,186 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from rich.console import Console
7
+
8
+ from . import __version__, display
9
+ from .hub_client import HeartbeatThread, HubClient
10
+ from .runner import WorkerRunner
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(
15
+ prog="kinenix-worker",
16
+ description="Kinenix Worker: Unattended Robot Daemon and Edge Execution Engine"
17
+ )
18
+ parser.add_argument(
19
+ "--version", "-v",
20
+ action="version",
21
+ version=f"kinenix-worker {__version__}"
22
+ )
23
+
24
+ subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
25
+
26
+ # Command: info
27
+ subparsers.add_parser("info", help="Display worker machine hardware, architecture, and runtime stats")
28
+
29
+ # Command: ping
30
+ subparsers.add_parser("ping", help="Check the connection and API key to the Hub (KINENIX_HUB_URL)")
31
+
32
+ # Command: run
33
+ run_parser = subparsers.add_parser("run", help="Execute a flow or project bundle on this worker")
34
+ run_parser.add_argument("flow_path", help="Path to flow.json or project bundle directory")
35
+ run_parser.add_argument("--sandbox", action="store_true", help="Execute inside an isolated temporary sandbox workspace")
36
+ run_parser.add_argument("--vars", type=str, help="JSON string of variables to inject (e.g. '{\"env\":\"prod\"}')")
37
+ run_parser.add_argument("--log-dir", type=str, help="Custom directory path to store execution logs")
38
+
39
+ # Command: watch
40
+ watch_parser = subparsers.add_parser("watch", help="Watch a directory and automatically trigger a flow when new files appear")
41
+ watch_parser.add_argument("directory", help="Directory path to watch")
42
+ watch_parser.add_argument("--flow", required=True, help="Path to flow.json or bundle to execute")
43
+ watch_parser.add_argument("--pattern", default="*.*", help="File matching pattern (e.g. '*.xlsx', '*.csv')")
44
+ watch_parser.add_argument("--interval", type=float, default=2.0, help="Polling interval in seconds")
45
+ watch_parser.add_argument("--sandbox", action="store_true", help="Execute in isolated sandbox workspace")
46
+ watch_parser.add_argument("--vars", type=str, help="JSON string of variables to inject")
47
+
48
+ # Command: schedule
49
+ sched_parser = subparsers.add_parser("schedule", help="Execute a flow on a scheduled time interval")
50
+ sched_parser.add_argument("--flow", required=True, help="Path to flow.json or bundle to execute")
51
+ sched_parser.add_argument("--interval", type=float, default=60.0, help="Interval in seconds between runs")
52
+ sched_parser.add_argument("--cron", type=str, default=None,
53
+ help="Five-field cron expression in local time, e.g. '0 8 1 * *' (overrides --interval)")
54
+ sched_parser.add_argument("--sandbox", action="store_true", help="Execute in isolated sandbox workspace")
55
+ sched_parser.add_argument("--vars", type=str, help="JSON string of variables to inject")
56
+
57
+ # Command: daemon
58
+ daemon_parser = subparsers.add_parser("daemon", help="Run multi-trigger daemon using a configuration file")
59
+ daemon_parser.add_argument("--config", default="triggers.json", help="Path to triggers.json configuration file")
60
+
61
+ args = parser.parse_args()
62
+
63
+ if not args.command:
64
+ parser.print_help()
65
+ sys.exit(0)
66
+
67
+ console = Console()
68
+ client = HubClient()
69
+ runner = WorkerRunner(client=client)
70
+
71
+ if args.command == "info":
72
+ display.print_info(console, runner.get_system_info(), client)
73
+ sys.exit(0)
74
+
75
+ if args.command == "ping":
76
+ with console.status(f"Contacting {client.url or 'Hub'}..."):
77
+ result = client.ping()
78
+ display.print_ping(console, client, result)
79
+ sys.exit(0 if result["authorized"] else 1)
80
+
81
+ def start_heartbeats() -> None:
82
+ if client.enabled:
83
+ HeartbeatThread(client).start()
84
+ console.print(f"[dim]Sending heartbeats to {client.url} as '{client.worker_id}'[/]")
85
+
86
+ if args.command == "run":
87
+ extra_vars = {}
88
+ if args.vars:
89
+ try:
90
+ extra_vars = json.loads(args.vars)
91
+ except Exception as e:
92
+ print(f"Error parsing --vars JSON: {e}", file=sys.stderr)
93
+ sys.exit(1)
94
+
95
+ # Keep heartbeats going during long flows so the worker is not shown as offline mid-run
96
+ start_heartbeats()
97
+ try:
98
+ res = runner.execute_flow(
99
+ flow_path_or_alias=args.flow_path,
100
+ extra_vars=extra_vars,
101
+ use_sandbox=args.sandbox,
102
+ log_dir=args.log_dir
103
+ )
104
+
105
+ display.print_run_result(console, res)
106
+ sys.exit(1 if res["has_error"] else 0)
107
+
108
+ except Exception as e:
109
+ print(f"Worker execution failed with error: {e}", file=sys.stderr)
110
+ sys.exit(1)
111
+
112
+ if args.command == "watch":
113
+ from .triggers.watcher import FileWatcherTrigger
114
+
115
+ extra_vars = {}
116
+ if args.vars:
117
+ try:
118
+ extra_vars = json.loads(args.vars)
119
+ except Exception as e:
120
+ print(f"Error parsing --vars JSON: {e}", file=sys.stderr)
121
+ sys.exit(1)
122
+
123
+ watcher = FileWatcherTrigger(
124
+ watch_dir=args.directory,
125
+ flow_path=args.flow,
126
+ pattern=args.pattern,
127
+ poll_interval=args.interval,
128
+ use_sandbox=args.sandbox,
129
+ extra_vars=extra_vars,
130
+ runner=runner
131
+ )
132
+ print(f"Watching directory '{args.directory}' for pattern '{args.pattern}'...")
133
+ print(f"Target flow: {args.flow}")
134
+ print("Press Ctrl+C to stop.")
135
+ start_heartbeats()
136
+ watcher.run_loop()
137
+ sys.exit(0)
138
+
139
+ if args.command == "schedule":
140
+ from .triggers.scheduler import CronSchedulerTrigger
141
+
142
+ extra_vars = {}
143
+ if args.vars:
144
+ try:
145
+ extra_vars = json.loads(args.vars)
146
+ except Exception as e:
147
+ print(f"Error parsing --vars JSON: {e}", file=sys.stderr)
148
+ sys.exit(1)
149
+
150
+ try:
151
+ scheduler = CronSchedulerTrigger(
152
+ flow_path=args.flow,
153
+ interval_seconds=args.interval,
154
+ use_sandbox=args.sandbox,
155
+ extra_vars=extra_vars,
156
+ runner=runner,
157
+ cron=args.cron
158
+ )
159
+ except ValueError as e:
160
+ print(f"Invalid --cron: {e}", file=sys.stderr)
161
+ sys.exit(1)
162
+ print(f"Scheduled flow '{args.flow}' to run {scheduler.describe()}...")
163
+ print("Press Ctrl+C to stop.")
164
+ start_heartbeats()
165
+ scheduler.run_loop()
166
+ sys.exit(0)
167
+
168
+ if args.command == "daemon":
169
+ from .triggers.manager import TriggerManager
170
+
171
+ manager = TriggerManager(runner=runner)
172
+ try:
173
+ manager.load_from_config(args.config)
174
+ print(f"Loaded triggers from '{args.config}'. Running daemon...")
175
+ print("Press Ctrl+C to stop.")
176
+ start_heartbeats()
177
+ manager.start_all(blocking=True)
178
+ sys.exit(0)
179
+ except Exception as e:
180
+ print(f"Failed to start trigger daemon: {e}", file=sys.stderr)
181
+ sys.exit(1)
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()
186
+