ultravisor 1.0.0 → 1.0.2

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 (96) hide show
  1. package/.babelrc +6 -0
  2. package/.browserslistrc +1 -0
  3. package/.browserslistrc-BACKUP +1 -0
  4. package/.gulpfile-quackage-config.json +7 -0
  5. package/.gulpfile-quackage.js +2 -0
  6. package/CONTRIBUTING.md +50 -0
  7. package/README.md +34 -0
  8. package/debug/Harness.js +2 -1
  9. package/docs/.nojekyll +0 -0
  10. package/docs/_sidebar.md +18 -0
  11. package/docs/_topbar.md +7 -0
  12. package/docs/architecture.md +103 -0
  13. package/docs/cover.md +15 -0
  14. package/docs/features/api.md +230 -0
  15. package/docs/features/cli.md +182 -0
  16. package/docs/features/configuration.md +245 -0
  17. package/docs/features/manifests.md +177 -0
  18. package/docs/features/operations.md +292 -0
  19. package/docs/features/scheduling.md +179 -0
  20. package/docs/features/tasks.md +1857 -0
  21. package/docs/index.html +39 -0
  22. package/docs/overview.md +75 -0
  23. package/docs/quickstart.md +167 -0
  24. package/docs/retold-catalog.json +24 -0
  25. package/docs/retold-keyword-index.json +19 -0
  26. package/package.json +5 -2
  27. package/source/Ultravisor.cjs +2 -2
  28. package/source/cli/Ultravisor-CLIProgram.cjs +38 -0
  29. package/source/cli/commands/Ultravisor-Command-ScheduleOperation.cjs +26 -2
  30. package/source/cli/commands/Ultravisor-Command-ScheduleTask.cjs +26 -2
  31. package/source/cli/commands/Ultravisor-Command-ScheduleView.cjs +22 -0
  32. package/source/cli/commands/Ultravisor-Command-SingleOperation.cjs +49 -1
  33. package/source/cli/commands/Ultravisor-Command-SingleTask.cjs +51 -1
  34. package/source/cli/commands/Ultravisor-Command-Stop.cjs +4 -0
  35. package/source/cli/commands/Ultravisor-Command-UpdateTask.cjs +91 -0
  36. package/source/config/Ultravisor-Default-Command-Configuration.cjs +6 -1
  37. package/source/services/Ultravisor-Hypervisor-Event-Base.cjs +18 -1
  38. package/source/services/Ultravisor-Hypervisor-State.cjs +213 -0
  39. package/source/services/Ultravisor-Hypervisor.cjs +225 -1
  40. package/source/services/Ultravisor-Operation-Manifest.cjs +150 -1
  41. package/source/services/Ultravisor-Operation.cjs +190 -1
  42. package/source/services/Ultravisor-Task.cjs +339 -1
  43. package/source/services/events/Ultravisor-Hypervisor-Event-Cron.cjs +71 -1
  44. package/source/services/tasks/Ultravisor-Task-Base.cjs +264 -0
  45. package/source/services/tasks/Ultravisor-Task-CollectValues.cjs +188 -0
  46. package/source/services/tasks/Ultravisor-Task-Command.cjs +65 -0
  47. package/source/services/tasks/Ultravisor-Task-CommandEach.cjs +190 -0
  48. package/source/services/tasks/Ultravisor-Task-Conditional.cjs +104 -0
  49. package/source/services/tasks/Ultravisor-Task-DateWindow.cjs +72 -0
  50. package/source/services/tasks/Ultravisor-Task-GeneratePagedOperation.cjs +336 -0
  51. package/source/services/tasks/Ultravisor-Task-LaunchOperation.cjs +143 -0
  52. package/source/services/tasks/Ultravisor-Task-LaunchTask.cjs +146 -0
  53. package/source/services/tasks/Ultravisor-Task-LineMatch.cjs +158 -0
  54. package/source/services/tasks/Ultravisor-Task-Request.cjs +56 -0
  55. package/source/services/tasks/Ultravisor-Task-Solver.cjs +89 -0
  56. package/source/services/tasks/Ultravisor-Task-TemplateString.cjs +93 -0
  57. package/source/services/tasks/rest/Ultravisor-Task-GetBinary.cjs +127 -0
  58. package/source/services/tasks/rest/Ultravisor-Task-GetJSON.cjs +119 -0
  59. package/source/services/tasks/rest/Ultravisor-Task-GetText.cjs +109 -0
  60. package/source/services/tasks/rest/Ultravisor-Task-GetXML.cjs +112 -0
  61. package/source/services/tasks/rest/Ultravisor-Task-RestRequest.cjs +499 -0
  62. package/source/services/tasks/rest/Ultravisor-Task-SendJSON.cjs +150 -0
  63. package/source/services/tasks/stagingfiles/Ultravisor-Task-CopyFile.cjs +110 -0
  64. package/source/services/tasks/stagingfiles/Ultravisor-Task-ListFiles.cjs +89 -0
  65. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadBinary.cjs +87 -0
  66. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadJSON.cjs +67 -0
  67. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadText.cjs +66 -0
  68. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadXML.cjs +69 -0
  69. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteBinary.cjs +95 -0
  70. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteJSON.cjs +96 -0
  71. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteText.cjs +99 -0
  72. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteXML.cjs +102 -0
  73. package/source/web_server/Ultravisor-API-Server.cjs +463 -3
  74. package/test/Ultravisor_tests.js +6097 -1
  75. package/webinterface/.babelrc +6 -0
  76. package/webinterface/.browserslistrc +1 -0
  77. package/webinterface/.browserslistrc-BACKUP +1 -0
  78. package/webinterface/.gulpfile-quackage-config.json +7 -0
  79. package/webinterface/.gulpfile-quackage.js +2 -0
  80. package/webinterface/css/ultravisor.css +121 -0
  81. package/webinterface/html/index.html +32 -0
  82. package/webinterface/package.json +39 -0
  83. package/webinterface/source/Pict-Application-Ultravisor-Configuration.json +15 -0
  84. package/webinterface/source/Pict-Application-Ultravisor.js +414 -0
  85. package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +42 -0
  86. package/webinterface/source/views/PictView-Ultravisor-BottomBar.js +65 -0
  87. package/webinterface/source/views/PictView-Ultravisor-Dashboard.js +236 -0
  88. package/webinterface/source/views/PictView-Ultravisor-Layout.js +83 -0
  89. package/webinterface/source/views/PictView-Ultravisor-ManifestList.js +273 -0
  90. package/webinterface/source/views/PictView-Ultravisor-OperationEdit.js +243 -0
  91. package/webinterface/source/views/PictView-Ultravisor-OperationList.js +141 -0
  92. package/webinterface/source/views/PictView-Ultravisor-Schedule.js +280 -0
  93. package/webinterface/source/views/PictView-Ultravisor-TaskEdit.js +220 -0
  94. package/webinterface/source/views/PictView-Ultravisor-TaskList.js +248 -0
  95. package/webinterface/source/views/PictView-Ultravisor-TimingView.js +420 -0
  96. package/webinterface/source/views/PictView-Ultravisor-TopBar.js +147 -0
@@ -0,0 +1,292 @@
1
+ # Operations
2
+
3
+ Operations compose multiple tasks into a sequential pipeline. When an
4
+ operation executes, its tasks run in order and a unified manifest tracks the
5
+ results of the entire run.
6
+
7
+ ## Operation Model
8
+
9
+ ```json
10
+ {
11
+ "GUIDOperation": "data-pipeline",
12
+ "Name": "Daily Data Pipeline",
13
+ "Tasks": [
14
+ "fetch-api-data",
15
+ "transform-data",
16
+ "load-into-db"
17
+ ]
18
+ }
19
+ ```
20
+
21
+ ### Fields
22
+
23
+ | Field | Required | Description |
24
+ |-------|----------|-------------|
25
+ | `GUIDOperation` | Yes | Unique identifier for the operation |
26
+ | `Name` | No | Human-readable name |
27
+ | `Tasks` | No | Ordered array of task GUIDs to execute |
28
+ | `GlobalState` | No | JSON object passed as context to all tasks |
29
+ | `NodeState` | No | Node-specific context passed to tasks |
30
+ | `StagingPath` | No | Override for the operation staging folder (see below) |
31
+
32
+ ## Execution Flow
33
+
34
+ 1. The operation's staging folder is created (or an explicit `StagingPath` is used)
35
+ 2. A manifest is created via the Manifest service
36
+ 3. `GlobalState` is exposed at `fable.AppData.GlobalState` so fable
37
+ services (e.g. ExpressionParser in Solver tasks) can access it
38
+ 4. Each task GUID in the `Tasks` array is looked up from state
39
+ 5. Tasks execute sequentially, with the staging folder set as context
40
+ 6. Each task result is added to the manifest
41
+ 7. After all tasks complete, the manifest is finalized
42
+ 8. A `Manifest_{GUIDOperation}.json` file is written to the staging folder
43
+ 9. Overall success is `true` only if every task succeeded
44
+
45
+ ## Per-Operation Staging Folder
46
+
47
+ Every operation automatically gets its own staging folder. This keeps
48
+ each operation's intermediate files, output and manifest isolated from
49
+ other operations.
50
+
51
+ The staging folder is created at:
52
+
53
+ ```
54
+ {UltravisorStagingRoot}/{GUIDOperation}/
55
+ ```
56
+
57
+ By default, `UltravisorStagingRoot` is `./dist/ultravisor_staging`
58
+ relative to the working directory. This can be changed in
59
+ `.ultravisor.json`:
60
+
61
+ ```json
62
+ {
63
+ "UltravisorStagingRoot": "/var/data/ultravisor_staging"
64
+ }
65
+ ```
66
+
67
+ All file-based task types (`WriteJSON`, `WriteText`, `WriteBinary`,
68
+ `ReadJSON`, `ReadText`, `ReadBinary`, `ListFiles`) resolve their `File`
69
+ paths relative to this staging folder. This means a task with
70
+ `"File": "output/report.json"` will write to
71
+ `{UltravisorStagingRoot}/{GUIDOperation}/output/report.json`.
72
+
73
+ When the operation completes, the manifest is written to:
74
+
75
+ ```
76
+ {UltravisorStagingRoot}/{GUIDOperation}/Manifest_{GUIDOperation}.json
77
+ ```
78
+
79
+ This provides a persistent on-disk record of everything the operation
80
+ produced.
81
+
82
+ ### Overriding the Staging Path
83
+
84
+ You can set an explicit `StagingPath` on the operation definition to
85
+ bypass the automatic per-GUID folder:
86
+
87
+ ```json
88
+ {
89
+ "GUIDOperation": "custom-output",
90
+ "Name": "Custom Output Location",
91
+ "Tasks": ["write-report"],
92
+ "StagingPath": "/mnt/shared/reports/2026-02"
93
+ }
94
+ ```
95
+
96
+ When `StagingPath` is set, the operation uses that path directly instead
97
+ of creating a subfolder under `UltravisorStagingRoot`.
98
+
99
+ If a task GUID is not found in state, the operation logs a warning and
100
+ continues with the next task. Task execution errors are captured in the
101
+ manifest without halting subsequent tasks.
102
+
103
+ ## Managing Operations
104
+
105
+ ### Via Configuration File
106
+
107
+ ```json
108
+ {
109
+ "Tasks": {
110
+ "pull-data": {
111
+ "GUIDTask": "pull-data",
112
+ "Name": "Pull Data from API",
113
+ "Type": "Request",
114
+ "URL": "https://api.example.com/export"
115
+ },
116
+ "process-data": {
117
+ "GUIDTask": "process-data",
118
+ "Name": "Process Downloaded Data",
119
+ "Type": "Command",
120
+ "Command": "python3 /scripts/process.py"
121
+ },
122
+ "notify": {
123
+ "GUIDTask": "notify",
124
+ "Name": "Send Notification",
125
+ "Type": "Command",
126
+ "Command": "echo 'Pipeline complete' | mail -s 'Done' admin@example.com"
127
+ }
128
+ },
129
+ "Operations": {
130
+ "etl-pipeline": {
131
+ "GUIDOperation": "etl-pipeline",
132
+ "Name": "ETL Pipeline",
133
+ "Tasks": ["pull-data", "process-data", "notify"]
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Via CLI
140
+
141
+ ```bash
142
+ # Run an operation immediately
143
+ ultravisor singleoperation etl-pipeline
144
+
145
+ # Dry run
146
+ ultravisor singleoperation etl-pipeline --dry_run
147
+ ```
148
+
149
+ ### Via API
150
+
151
+ ```bash
152
+ # Create an operation
153
+ curl -X POST http://localhost:54321/Operation \
154
+ -H "Content-Type: application/json" \
155
+ -d '{
156
+ "GUIDOperation": "etl-pipeline",
157
+ "Name": "ETL Pipeline",
158
+ "Tasks": ["pull-data", "process-data", "notify"]
159
+ }'
160
+
161
+ # Get one
162
+ curl http://localhost:54321/Operation/etl-pipeline
163
+
164
+ # List all
165
+ curl http://localhost:54321/Operation
166
+
167
+ # Update
168
+ curl -X PUT http://localhost:54321/Operation/etl-pipeline \
169
+ -H "Content-Type: application/json" \
170
+ -d '{"Name": "Updated ETL Pipeline", "Tasks": ["pull-data", "process-data"]}'
171
+
172
+ # Delete
173
+ curl -X DELETE http://localhost:54321/Operation/etl-pipeline
174
+
175
+ # Execute
176
+ curl http://localhost:54321/Operation/etl-pipeline/Execute
177
+ ```
178
+
179
+ ## Operation Execution Result
180
+
181
+ Executing an operation returns a manifest. The `StagingPath` field shows
182
+ where the operation's files were written, and `ManifestFilePath` shows
183
+ the location of the manifest JSON on disk:
184
+
185
+ ```json
186
+ {
187
+ "GUIDOperation": "etl-pipeline",
188
+ "GUIDRun": "etl-pipeline-1707566400000",
189
+ "Name": "ETL Pipeline",
190
+ "StagingPath": "/var/data/ultravisor_staging/etl-pipeline",
191
+ "ManifestFilePath": "/var/data/ultravisor_staging/etl-pipeline/Manifest_etl-pipeline.json",
192
+ "StartTime": "2026-02-10T12:00:00.000Z",
193
+ "StopTime": "2026-02-10T12:00:05.200Z",
194
+ "Status": "Complete",
195
+ "Success": true,
196
+ "Summary": "Operation etl-pipeline Complete: 3 task(s) executed.",
197
+ "TaskResults": [
198
+ {
199
+ "GUIDTask": "pull-data",
200
+ "Status": "Complete",
201
+ "Success": true,
202
+ "StartTime": "...",
203
+ "StopTime": "..."
204
+ },
205
+ {
206
+ "GUIDTask": "process-data",
207
+ "Status": "Complete",
208
+ "Success": true,
209
+ "StartTime": "...",
210
+ "StopTime": "..."
211
+ },
212
+ {
213
+ "GUIDTask": "notify",
214
+ "Status": "Complete",
215
+ "Success": true,
216
+ "StartTime": "...",
217
+ "StopTime": "..."
218
+ }
219
+ ],
220
+ "Log": [
221
+ "Operation etl-pipeline started at 2026-02-10T12:00:00.000Z",
222
+ "Task pull-data completed with status: Complete",
223
+ "Task process-data completed with status: Complete",
224
+ "Task notify completed with status: Complete",
225
+ "Operation etl-pipeline Complete: 3 task(s) executed."
226
+ ]
227
+ }
228
+ ```
229
+
230
+ ## Examples
231
+
232
+ ### System health check
233
+
234
+ ```json
235
+ {
236
+ "Tasks": {
237
+ "check-disk": {
238
+ "GUIDTask": "check-disk",
239
+ "Type": "Command",
240
+ "Command": "df -h /"
241
+ },
242
+ "check-memory": {
243
+ "GUIDTask": "check-memory",
244
+ "Type": "Command",
245
+ "Command": "vm_stat"
246
+ },
247
+ "check-load": {
248
+ "GUIDTask": "check-load",
249
+ "Type": "Command",
250
+ "Command": "uptime"
251
+ }
252
+ },
253
+ "Operations": {
254
+ "health-check": {
255
+ "GUIDOperation": "health-check",
256
+ "Name": "System Health Check",
257
+ "Tasks": ["check-disk", "check-memory", "check-load"]
258
+ }
259
+ }
260
+ }
261
+ ```
262
+
263
+ ### Image generation pipeline
264
+
265
+ ```json
266
+ {
267
+ "Tasks": {
268
+ "generate-prompt": {
269
+ "GUIDTask": "generate-prompt",
270
+ "Type": "Command",
271
+ "Command": "python3 /scripts/random_prompt.py > /tmp/prompt.txt"
272
+ },
273
+ "generate-image": {
274
+ "GUIDTask": "generate-image",
275
+ "Type": "Command",
276
+ "Command": "python3 /scripts/generate_image.py --prompt-file /tmp/prompt.txt --output /output/image.png"
277
+ },
278
+ "upload-image": {
279
+ "GUIDTask": "upload-image",
280
+ "Type": "Command",
281
+ "Command": "aws s3 cp /output/image.png s3://my-bucket/images/"
282
+ }
283
+ },
284
+ "Operations": {
285
+ "image-gen": {
286
+ "GUIDOperation": "image-gen",
287
+ "Name": "Generate and Upload Image",
288
+ "Tasks": ["generate-prompt", "generate-image", "upload-image"]
289
+ }
290
+ }
291
+ }
292
+ ```
@@ -0,0 +1,179 @@
1
+ # Scheduling
2
+
3
+ Ultravisor uses cron expressions to schedule recurring execution of tasks
4
+ and operations. The Hypervisor service manages schedule entries, and the
5
+ Cron Event service creates the actual cron jobs.
6
+
7
+ ## Schedule Types
8
+
9
+ | Type | Default Expression | Description |
10
+ |------|-------------------|-------------|
11
+ | `cron` | `0 * * * *` | Standard cron expression |
12
+ | `hourly` | `0 * * * *` | Every hour at minute 0 |
13
+ | `daily` | `0 0 * * *` | Every day at midnight |
14
+
15
+ When you specify `daily` or `hourly`, the parameters field is still used as
16
+ the actual cron expression if provided. The type mainly sets the default
17
+ fallback.
18
+
19
+ ## Cron Expression Format
20
+
21
+ Ultravisor uses the standard 5-field cron format:
22
+
23
+ ```
24
+ * * * * *
25
+ | | | | |
26
+ | | | | +--- Day of week (0-7, Sun=0 or 7)
27
+ | | | +-------- Month (1-12)
28
+ | | +------------- Day of month (1-31)
29
+ | +------------------ Hour (0-23)
30
+ +----------------------- Minute (0-59)
31
+ ```
32
+
33
+ The `cron` library also supports a 6-field format with seconds:
34
+
35
+ ```
36
+ * * * * * *
37
+ | | | | | |
38
+ | | | | | +--- Day of week
39
+ | | | | +-------- Month
40
+ | | | +------------- Day of month
41
+ | | +------------------ Hour
42
+ | +----------------------- Minute
43
+ +---------------------------- Second (0-59)
44
+ ```
45
+
46
+ ### Common Expressions
47
+
48
+ | Expression | Meaning |
49
+ |------------|---------|
50
+ | `* * * * *` | Every minute |
51
+ | `*/5 * * * *` | Every 5 minutes |
52
+ | `0 * * * *` | Every hour |
53
+ | `0 */6 * * *` | Every 6 hours |
54
+ | `0 0 * * *` | Daily at midnight |
55
+ | `0 9 * * 1-5` | Weekdays at 9:00 AM |
56
+ | `0 0 1 * *` | First day of each month |
57
+ | `30 2 * * 0` | Sundays at 2:30 AM |
58
+
59
+ ## Schedule Entry Model
60
+
61
+ Each schedule entry has this structure:
62
+
63
+ ```json
64
+ {
65
+ "GUID": "sched-task-disk-usage-1707566400000",
66
+ "TargetType": "Task",
67
+ "TargetGUID": "disk-usage",
68
+ "ScheduleType": "cron",
69
+ "Parameters": "*/5 * * * *",
70
+ "CronExpression": "*/5 * * * *",
71
+ "Active": false,
72
+ "CreatedAt": "2026-02-10T12:00:00.000Z"
73
+ }
74
+ ```
75
+
76
+ | Field | Description |
77
+ |-------|-------------|
78
+ | `GUID` | Auto-generated unique ID for this schedule entry |
79
+ | `TargetType` | `Task` or `Operation` |
80
+ | `TargetGUID` | The GUID of the task or operation to execute |
81
+ | `ScheduleType` | `cron`, `daily`, or `hourly` |
82
+ | `Parameters` | The raw parameters (usually a cron expression) |
83
+ | `CronExpression` | The resolved cron expression used for scheduling |
84
+ | `Active` | Whether the cron job is currently running |
85
+ | `CreatedAt` | ISO timestamp of when the entry was created |
86
+
87
+ ## Managing the Schedule
88
+
89
+ ### Via CLI
90
+
91
+ ```bash
92
+ # Schedule a task
93
+ ultravisor schedule_task disk-usage -t cron -p "*/5 * * * *"
94
+
95
+ # Schedule an operation
96
+ ultravisor schedule_operation etl-pipeline -t daily -p "0 2 * * *"
97
+
98
+ # View schedule
99
+ ultravisor schedule
100
+
101
+ # Stop all scheduled jobs
102
+ ultravisor stop
103
+ ```
104
+
105
+ ### Via API
106
+
107
+ ```bash
108
+ # View schedule
109
+ curl http://localhost:54321/Schedule
110
+
111
+ # Schedule a task
112
+ curl -X POST http://localhost:54321/Schedule/Task \
113
+ -H "Content-Type: application/json" \
114
+ -d '{
115
+ "GUIDTask": "disk-usage",
116
+ "ScheduleType": "cron",
117
+ "Parameters": "*/5 * * * *"
118
+ }'
119
+
120
+ # Schedule an operation
121
+ curl -X POST http://localhost:54321/Schedule/Operation \
122
+ -H "Content-Type: application/json" \
123
+ -d '{
124
+ "GUIDOperation": "etl-pipeline",
125
+ "ScheduleType": "daily",
126
+ "Parameters": "0 2 * * *"
127
+ }'
128
+
129
+ # Remove a schedule entry
130
+ curl -X DELETE http://localhost:54321/Schedule/sched-task-disk-usage-1707566400000
131
+
132
+ # Start all scheduled jobs
133
+ curl http://localhost:54321/Schedule/Start
134
+
135
+ # Stop all scheduled jobs
136
+ curl http://localhost:54321/Schedule/Stop
137
+ ```
138
+
139
+ ## Lifecycle
140
+
141
+ 1. **Create** -- `scheduleTask()` or `scheduleOperation()` adds an entry to
142
+ the in-memory schedule with `Active: false`
143
+ 2. **Start** -- `startSchedule()` iterates all inactive entries, marks them
144
+ active, and creates a cron job for each
145
+ 3. **Tick** -- on each cron tick, the target task or operation is looked up
146
+ from state and executed
147
+ 4. **Stop** -- `stopSchedule()` stops all cron jobs and marks entries inactive
148
+ 5. **Remove** -- `removeScheduleEntry()` stops the job (if active) and
149
+ removes the entry from the schedule
150
+
151
+ ## Examples
152
+
153
+ ### Run a backup every night at 2 AM
154
+
155
+ ```bash
156
+ ultravisor schedule_task nightly-backup -t cron -p "0 2 * * *"
157
+ ```
158
+
159
+ ### Monitor disk every 10 minutes
160
+
161
+ ```json
162
+ {
163
+ "GUIDTask": "disk-monitor",
164
+ "ScheduleType": "cron",
165
+ "Parameters": "*/10 * * * *"
166
+ }
167
+ ```
168
+
169
+ ```bash
170
+ curl -X POST http://localhost:54321/Schedule/Task \
171
+ -H "Content-Type: application/json" \
172
+ -d '{"GUIDTask":"disk-monitor","ScheduleType":"cron","Parameters":"*/10 * * * *"}'
173
+ ```
174
+
175
+ ### Run an ETL pipeline on weekdays at 6 AM
176
+
177
+ ```bash
178
+ ultravisor schedule_operation etl-pipeline -t cron -p "0 6 * * 1-5"
179
+ ```