@pixlcore/xycli 1.0.0

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 (63) hide show
  1. package/README.md +543 -0
  2. package/docs/help.md +2587 -0
  3. package/docs/screenshots.md +219 -0
  4. package/docs/sync.md +920 -0
  5. package/docs/tutorial.md +204 -0
  6. package/index.js +444 -0
  7. package/lib/alerts.js +735 -0
  8. package/lib/apikey.js +425 -0
  9. package/lib/buckets.js +607 -0
  10. package/lib/categories.js +323 -0
  11. package/lib/channels.js +290 -0
  12. package/lib/config.js +86 -0
  13. package/lib/dashboard.js +237 -0
  14. package/lib/doc.js +131 -0
  15. package/lib/events.js +1314 -0
  16. package/lib/groups.js +1038 -0
  17. package/lib/jobs.js +1254 -0
  18. package/lib/log.js +125 -0
  19. package/lib/marketplace.js +386 -0
  20. package/lib/monitors.js +412 -0
  21. package/lib/plugins.js +491 -0
  22. package/lib/secrets.js +441 -0
  23. package/lib/servers.js +1502 -0
  24. package/lib/snapshots.js +498 -0
  25. package/lib/sync.js +963 -0
  26. package/lib/system.js +612 -0
  27. package/lib/tags.js +250 -0
  28. package/lib/tickets.js +681 -0
  29. package/lib/transfer.js +447 -0
  30. package/lib/utils.js +2745 -0
  31. package/lib/webhook.js +414 -0
  32. package/logo.webp +0 -0
  33. package/package.json +38 -0
  34. package/test/README.md +97 -0
  35. package/test/api-validation.test.js +170 -0
  36. package/test/categories.test.js +246 -0
  37. package/test/channels.test.js +209 -0
  38. package/test/config.test.js +96 -0
  39. package/test/doc.test.js +84 -0
  40. package/test/groups.test.js +211 -0
  41. package/test/help.test.js +26 -0
  42. package/test/helpers/common.js +127 -0
  43. package/test/helpers/pagination-preload.cjs +18 -0
  44. package/test/log.test.js +103 -0
  45. package/test/markdown.test.js +61 -0
  46. package/test/marketplace.test.js +229 -0
  47. package/test/monitors.test.js +227 -0
  48. package/test/pagination.test.js +138 -0
  49. package/test/plugins.test.js +351 -0
  50. package/test/secrets.test.js +271 -0
  51. package/test/servers.test.js +303 -0
  52. package/test/snapshots.test.js +167 -0
  53. package/test/sync-unit.test.js +446 -0
  54. package/test/sync.test.js +542 -0
  55. package/test/system.test.js +94 -0
  56. package/test/tags.test.js +230 -0
  57. package/test/tickets.test.js +261 -0
  58. package/test/toast.test.js +73 -0
  59. package/test/transfer-apikey.test.js +96 -0
  60. package/test/transfer-workflow.test.js +122 -0
  61. package/test/transfer.test.js +314 -0
  62. package/test/webhook.test.js +332 -0
  63. package/test.js +49 -0
package/docs/sync.md ADDED
@@ -0,0 +1,920 @@
1
+ # Filesystem Sync Guide
2
+
3
+ Filesystem sync connects your existing xyOps automation with local files. Keep Event definitions and Plugin scripts in Git, edit them with your favorite tools, review changes as diffs, and send them to xyOps. You can also pull changes made in the web interface back into your local tree.
4
+
5
+ **Your file and directory layout is completely free-form.** Organize definitions into any folders you like and give their JSON files any names you want. The sync engine searches recursively and identifies each definition by the type and ID inside the file, so its contents determine what it syncs. You can move or rename files later without changing their identity or triggering a sync. Keep the `.json` extension, and move or rename any external property files alongside their matching definition so they remain linked.
6
+
7
+ This guide covers initial setup, file layouts, every sync option, saved configuration, one-way and two-way operation, deletion, completion commands, notifications, and automatic runs.
8
+
9
+ **Sync command reference:** [Sync](help.md#sync), [Sync setup](help.md#sync-setup), and [CLI configuration](help.md#config).
10
+
11
+ ## Contents
12
+
13
+ - [Choose your workflow](#choose-your-workflow)
14
+ - [Supported resources and requirements](#supported-resources-and-requirements)
15
+ - [Set up your first sync tree](#set-up-your-first-sync-tree)
16
+ - [Files, folders, and external properties](#files-folders-and-external-properties)
17
+ - [Run sync and review differences](#run-sync-and-review-differences)
18
+ - [Configuration and option reference](#configuration-and-option-reference)
19
+ - [Two-way sync and modification times](#two-way-sync-and-modification-times)
20
+ - [Remote-management warnings](#remote-management-warnings)
21
+ - [Delete mode](#delete-mode)
22
+ - [Completion commands](#completion-commands)
23
+ - [Error notifications](#error-notifications)
24
+ - [Built-in PID locking](#built-in-pid-locking)
25
+ - [Automatic sync with cron](#automatic-sync-with-cron)
26
+ - [Automatic upsync with Git hooks](#automatic-upsync-with-git-hooks)
27
+ - [Automatic upsync with GitHub Actions](#automatic-upsync-with-github-actions)
28
+ - [Troubleshooting and operational limits](#troubleshooting-and-operational-limits)
29
+
30
+ ## Choose your workflow
31
+
32
+ Sync has two directions. **Up** sends local definitions to xyOps. **Down** writes xyOps definitions back to local files. You select directions independently for each resource type.
33
+
34
+ | Workflow | Typical command | Use it when |
35
+ | --- | --- | --- |
36
+ | Local files are authoritative | `xy sync ./ --up events,plugins` | You want editor and Git changes to determine the saved definitions in xyOps. |
37
+ | xyOps is authoritative | `xy sync ./ --down events,plugins` | You edit in the web interface and want a local history or backup of selected definitions. |
38
+ | Different sources for different types | `xy sync ./ --up plugins --down events` | Developers own Plugin source while operators manage Event definitions in xyOps. |
39
+ | Both sides can change | `xy sync ./ --up events,plugins --down events,plugins` | You accept modification-time conflict handling and have a persistent working directory. |
40
+
41
+ **Sync applies changes immediately. There is no confirmation step.** Add `--dry` to preview a run before allowing it to change xyOps or your files.
42
+
43
+ Sync updates *changes* to objects that already exist on both sides. It does not create new xyOps objects, generate local files for newly created objects (except during [setup](#set-up-your-first-sync-tree)), or remove local files when objects disappear from xyOps. Use resource creation commands or imports to create objects, and setup or exports to introduce their local files.
44
+
45
+ For a Git-based deployment workflow, start with up-only sync. A fresh checkout is a good source for an explicit push, but its filesystem timestamps are not reliable evidence for two-way conflict decisions.
46
+
47
+ ## Supported resources and requirements
48
+
49
+ Install and connect xyCLI as described in [Getting started](../README.md#getting-started). The CLI requires Node.js 20.19.0 or later and xyOps 1.0.96 or later. The machine running sync needs network access to the xyOps API and access to the local directories you select.
50
+
51
+ Use these names with `--up`, `--down`, `--delete`, and setup:
52
+
53
+ | Selection name | Definitions |
54
+ | --- | --- |
55
+ | `alerts` | Alert definitions, rather than their invocations. |
56
+ | `api_keys` | API Key definitions. |
57
+ | `categories` | Event Categories. |
58
+ | `channels` | Notification Channels. |
59
+ | `events` | Events and workflows. |
60
+ | `groups` | Server Groups. |
61
+ | `monitors` | Monitor definitions. |
62
+ | `plugins` | Event, Monitor, Action, and Scheduler Plugins. |
63
+ | `tags` | Tag definitions. |
64
+ | `web_hooks` | Web Hook definitions. |
65
+
66
+ `all` selects every supported type. Supply multiple types as a comma-separated string without spaces, such as `events,plugins,categories`.
67
+
68
+ Buckets, Secret Vaults, Users, and Roles are not supported by sync. Individual workers, jobs, Tickets, Alert history, and snapshots are also outside its definition-sync scope. Workflows use the `events` selection even though setup places them in a separate folder (for convenience).
69
+
70
+ ### Permissions
71
+
72
+ Your API Key needs permission to read the definitions you scan and to edit each type you push. Delete mode additionally requires the corresponding delete privileges. Resource restrictions, such as Category and Group access, still apply.
73
+
74
+ Sync also maintains remote-management tracking which requires the [update_state](https://docs.xyops.io/privileges/update_state) privilege. A pull or two-way run can update this tracking too. Notification email requires the [send_emails](https://docs.xyops.io/privileges/send_emails) privilege; an error Event requires the [run_jobs](https://docs.xyops.io/privileges/run_jobs) privilege, permission to use that Event's resources, and an enabled manual trigger.
75
+
76
+ Before using deletion, ensure your key can see the entire inventory for every selected type. A restricted view is not an adequate basis for maintaining a complete tree.
77
+
78
+ **Learn more about xyOps:** [xyOps API Keys](https://docs.xyops.io/api#api-keys) and [xyOps Privileges](https://docs.xyops.io/privileges).
79
+
80
+ ## Set up your first sync tree
81
+
82
+ Setup exports existing definitions into a local starter tree. The `sync setup` command does not change anything in xyOps, and it is separate from a regular sync run.
83
+
84
+ ### 1. Start with a fresh directory
85
+
86
+ ```sh
87
+ mkdir xyops-automation
88
+ cd xyops-automation
89
+ ```
90
+
91
+ Setup always writes below the current working directory. Type names after `setup` are resource selections, not destination paths. The `base_dirs` configuration property and direction settings do not choose setup's destination.
92
+
93
+ ### 2. Preview the export
94
+
95
+ ```sh
96
+ xy sync setup events plugins categories --file_props script,params.script --dry
97
+ ```
98
+
99
+ The `--file_props` switch will externalize specific JSON property paths into individually named files, for easy editing. This is great for storing Plugin scripts and Event shell scripts in separate files alongside the main JSON definition files.
100
+
101
+ The preview lists the folders and files that would be written. `--dry` creates no directories or files.
102
+
103
+ ### 3. Write the files
104
+
105
+ ```sh
106
+ xy sync setup events plugins categories --file_props script,params.script
107
+ ```
108
+
109
+ Depending on your saved definitions, the result might look like this:
110
+
111
+ ```text
112
+ xyops-automation/
113
+ categories/
114
+ Backups.json
115
+ events/
116
+ Nightly-Backup.json
117
+ Nightly-Backup-params.script.sh
118
+ plugins/
119
+ Custom-Runner.json
120
+ Custom-Runner-script.js
121
+ workflows/
122
+ Release-Pipeline.json
123
+ ```
124
+
125
+ Setup creates one folder per selected resource type and one JSON file per definition. Event and workflow exports are separated into `events/` and `workflows/`. Both participate in normal sync through the `events` selection.
126
+
127
+ Filenames come from each definition's title: sequences of non-word characters become hyphens, and leading or trailing hyphens are removed. For example, `Nightly Backup` becomes `Nightly-Backup.json`. Different titles can produce the same filename, so review the preview for collisions.
128
+
129
+ ### 4. Review and version the tree
130
+
131
+ Optionally use git to version your files. You can actually use any SCM you want, or none at all. The sync engine works on raw files, and doesn't care how they are managed.
132
+
133
+ ```sh
134
+ git init
135
+ git add .
136
+ git commit -m "Add xyOps automation definitions"
137
+ xy sync ./ --up events,plugins,categories --dry
138
+ ```
139
+
140
+ Review the generated definitions, scripts, and any embedded request headers or credentials before sharing the repository.
141
+
142
+ ### Export stock and Marketplace definitions
143
+
144
+ Setup normally omits objects marked as stock or installed from the Marketplace. To include them:
145
+
146
+ ```sh
147
+ xy sync setup all --stock --marketplace --file_props script,params.script
148
+ ```
149
+
150
+ These switches are optional export choices, not requirements for delete mode. Objects with a `stock` or `marketplace` property are always excluded from sync deletion, even when they have no local file. Leave Marketplace Plugins managed by the Marketplace and its upgrade system; a standard sync tree can focus on definitions you maintain yourself.
151
+
152
+ ### Rerun setup carefully
153
+
154
+ Without `--force`, setup stops if an output file already exists. Earlier files written during that run remain; setup is not an all-or-nothing transaction.
155
+
156
+ ```sh
157
+ xy sync setup events plugins --force
158
+ ```
159
+
160
+ `--force` allows generated JSON and external files to be overwritten. It does not clean up old filenames, solve filename collisions, or preserve your local edits. Export into a fresh directory when adding newly created objects, then move their reviewed files into the existing tree.
161
+
162
+ Alternatively, you can export single objects at a time:
163
+
164
+ ```sh
165
+ xy event EVENT_ID --export ./events/New-Event.json
166
+ ```
167
+
168
+ Keep this export to one object, without `--deps`. Multi-object exports cannot be used as a sync source.
169
+
170
+ ## Files, folders, and external properties
171
+
172
+ ### One definition per XYPDF file
173
+
174
+ Sync sources are plain `.json` files containing exactly one XYPDF item. A minimal illustration is:
175
+
176
+ ```json
177
+ {
178
+ "type": "xypdf",
179
+ "description": "xyOps Portable Data Object",
180
+ "version": "1.0",
181
+ "xyops": "1.0.96",
182
+ "items": [
183
+ {
184
+ "type": "category",
185
+ "data": {
186
+ "id": "EXISTING_CATEGORY_ID",
187
+ "title": "Backups",
188
+ "enabled": true,
189
+ "color": "blue",
190
+ "actions": [],
191
+ "limits": []
192
+ }
193
+ }
194
+ ]
195
+ }
196
+ ```
197
+
198
+ Normally you should let setup or export generate the wrapper. Keep the object's original `data.id`: sync matches the **item type and exact ID**, not the title, filename, or folder name.
199
+
200
+ The optional `xyops` field declares a minimum required xyOps version. Sync rejects a source requiring a newer version than your instance. The `version` field is the XYPDF format version (should be `1.0`).
201
+
202
+ Gzip-compressed exports and multi-item XYPDF files cannot be sync sources. Sync files may still contain sensitive definition data, such as scripts or Web Hook headers.
203
+
204
+ ### Organize folders your way
205
+
206
+ Normal sync scans the selected directories recursively. You may move or rename definitions to organize them by project, environment, or team:
207
+
208
+ ```sh
209
+ xy sync ./team-a ./team-b --up events,plugins --dry
210
+ ```
211
+
212
+ Keep one source per resource type and ID, and avoid overlapping base directories. The engine detects repeated sources for the same type and ID, emits a warning naming each later duplicate file, and skips those duplicates during scanning. Identical copies count as duplicates too; equal IDs in different resource types do not.
213
+
214
+ As with other scan warnings, a duplicate warning stops the entire run before definition updates, downloads, tracking changes, or deletion. Remove the extra sources or overlapping scan paths, then run sync again.
215
+
216
+ Hidden files and hidden directories are skipped, including `.git/`. However, note that a `.gitignore` rule does not control sync discovery. Other valid JSON files without an XYPDF wrapper are skipped, but malformed `.json` files generate warnings. Keep unrelated JSON files and dependency directories outside the scanned tree where possible.
217
+
218
+ ### Keep scripts in external files
219
+
220
+ `--file_props` tells setup to extract selected nonempty string properties. For example:
221
+
222
+ ```sh
223
+ xy sync setup events plugins --file_props script,params.script
224
+ ```
225
+
226
+ This extracts the selected content, if found, and moves it to a neighboring file:
227
+
228
+ | Definition | Property | Neighbor file |
229
+ | --- | --- | --- |
230
+ | `My-Plugin.json` | `script` | `My-Plugin-script.js` |
231
+ | `My-Event.json` | `params.script` | `My-Event-params.script.sh` |
232
+
233
+ The general naming pattern is **`BASENAME-PROPERTY.EXT`**, in the same directory as `BASENAME.json`. Dot paths such as `params.script` identify nested properties. Setup chooses an extension from the executable command or a script's shebang when possible, falling back to JSON-looking content or `.txt`. Normal sync accepts your chosen extension.
234
+
235
+ External properties are detected automatically when sync runs; you do not need to repeat `--file_props`. The neighbor's text replaces that property in the local definition before comparison and before an upload. A pull writes the latest value into the existing neighbor file.
236
+
237
+ You can use this for other string properties too, such as `body` or `notes`. Keep only one neighbor file per unique property path. A neighbor must resolve to a valid property path, and multiple matching neighbors are not treated as alternative versions.
238
+
239
+ Rename a definition and all its neighbors together. Do not delete an external file while leaving the placeholder `(External)` text as its saved value: the marker itself is ordinary text, and can be pushed as the property's content if no neighbor replaces it. To return to an inline property, put the real content back into the JSON and remove the neighbor.
240
+
241
+ ### What sync compares and writes
242
+
243
+ Comparisons ignore auto-generated properties like `created`, `modified`, `revision`, `sort_order`, and `username`. Object key order does not create a change. Arrays and definition values remain meaningful, so changes to action lists, triggers, or parameters can produce updates.
244
+
245
+ Downsync writes pretty-printed XYPDF JSON and removes those server-maintained metadata fields. It keeps the existing local filename, even if the object's title changes in xyOps. Ordinary downsync does not extract new properties or invent new neighbor files; setup controls initial extraction.
246
+
247
+ ## Run sync and review differences
248
+
249
+ ### Push local changes
250
+
251
+ Edit an Event's JSON or external script, then preview:
252
+
253
+ ```sh
254
+ xy sync ./ --up events,plugins,categories --dry
255
+ ```
256
+
257
+ Remove `--dry` after reviewing it to actually perform the sync:
258
+
259
+ ```sh
260
+ xy sync ./ --up events,plugins,categories
261
+ ```
262
+
263
+ Updates use the resource API, so normal validation and permissions apply. Saved enabled schedules and actions remain active.
264
+
265
+ ### Pull xyOps changes
266
+
267
+ This showcases running a sync in the oppostite direction, known as a "down sync". This will only update existing local files -- it will not create new ones.
268
+
269
+ ```sh
270
+ xy sync ./ --down events,plugins,categories --dry
271
+ xy sync ./ --down events,plugins,categories
272
+ ```
273
+
274
+ Down-only mode replaces differing local definitions with the xyOps versions, including existing external properties, regardless of file modification times. Commit or otherwise preserve local edits you want to keep before pulling.
275
+
276
+ ### Use full diffs
277
+
278
+ ```sh
279
+ xy sync ./ --up events,plugins --dry --verbose
280
+ ```
281
+
282
+ Normal output shortens unchanged diff context. `--verbose` shows full context and, for API operations, request and response details. A downsync preview also shows the XYPDF JSON that would be written.
283
+
284
+ `--dry` still connects, reads definitions, and scans files. It does not update xyOps, write local files, update sync tracking, run completion commands, or send error notifications.
285
+
286
+ ### Understand scan failures
287
+
288
+ Sync scans and validates the sources before it begins updates or deletion. A missing base directory, malformed JSON or XYPDF source, unsupported item, missing ID, incompatible version, missing remote definition, duplicate source, or invalid neighbor stops that run before changes begin. This also applies to problematic XYPDF files whose type you did not select for a direction: the scan validates the entire tree first.
289
+
290
+ After changes begin, individual API or write failures are logged and the engine can continue with other objects.
291
+
292
+ ## Configuration and option reference
293
+
294
+ ### Save defaults in a CLI config file
295
+
296
+ Put a `sync` object in `/etc/xyops/cli.json` for machine or service defaults, or `~/.config/xyops/cli.json` for your own user account:
297
+
298
+ ```json
299
+ {
300
+ "base_url": "https://xyops.example.com",
301
+ "api_key": "YOUR_API_KEY",
302
+ "sync": {
303
+ "up": ["events", "plugins", "categories"],
304
+ "down": false,
305
+ "delete": false,
306
+ "cmd_timeout": 30,
307
+ "error_email": "ops@example.com",
308
+ "error_event": "SYNC_ERROR_EVENT_ID"
309
+ }
310
+ }
311
+ ```
312
+
313
+ Replace the URL, credentials, address, and Event ID with your own values; omit notification settings you do not want to use. With these defaults, `xy sync ./ --dry` previews the configured push.
314
+
315
+ CLI config loads the system file, then the user file, then `XYOPS_` environment variables. Matching top-level keys replace earlier values. A user-level `sync` object will replace a system-level `sync` object if both are present.
316
+
317
+ Use environment variables such as `XYOPS_BASE_URL` and `XYOPS_API_KEY` for connection credentials when appropriate.
318
+
319
+ ### Override defaults for one run
320
+
321
+ Ordinary sync merges command-line options over the saved `sync` object. Top-level sync options replace their saved values:
322
+
323
+ ```sh
324
+ xy sync ./ --up events --down false --delete false --dry
325
+ xy sync ./ --up false --down events,plugins --delete false --dry
326
+ xy sync ./ --up all --down all --delete false --dry
327
+ ```
328
+
329
+ Use the literal `false` to disable a saved direction, deletion, or completion command. Lists can be JSON arrays or comma-separated strings; `all` and boolean `true` select all supported types. A command-line selection replaces the saved selection rather than adding to it.
330
+
331
+ ### Change your saved user settings
332
+
333
+ `xy config` applies only the supplied changes to the user config file:
334
+
335
+ ```sh
336
+ xy config --sync.up events,plugins --sync.down false --sync.delete false
337
+ xy config --sync.cmd_timeout 60
338
+ xy config --sync.error_email ops@example.com
339
+ xy config --sync.down_cmd "git add . && git commit -m 'Sync from xyOps'"
340
+ xy config --sync.lock_file /run/xyops/xycli-sync.pid
341
+ ```
342
+
343
+ For nested arrays and other structured settings, editing the JSON config file directly is also convenient. Keep the complete desired `sync` object in the user file if you otherwise rely on system-level sync defaults.
344
+
345
+ ### Directories and working directory
346
+
347
+ Specify one or more base directories to scan, followed by any switches you need:
348
+
349
+ ```sh
350
+ xy sync /srv/xyops-sync --up events,plugins
351
+ ```
352
+
353
+ You can optionally specify the base dirs in your config file as `base_dirs`. This is used when no base directories are specified on the command-line.
354
+
355
+ ```json
356
+ {
357
+ "sync": {
358
+ "base_dirs": ["/srv/xyops-sync"],
359
+ "up": ["events", "plugins"],
360
+ "down": false,
361
+ "delete": false
362
+ }
363
+ }
364
+ ```
365
+
366
+ Relative paths resolve against the CLI's current working directory. Completion commands run in the first selected base directory.
367
+
368
+ ### Common sync options
369
+
370
+ | Option | Config property | Default | Behavior |
371
+ | --- | --- | --- | --- |
372
+ | `--up TYPES` | `sync.up` | Disabled | Sync upwards to xyOps from local sources for these types. |
373
+ | `--down TYPES` | `sync.down` | Disabled | Down downwards to existing local sources from xyOps for these types. |
374
+ | `--delete TYPES` | `sync.delete` | Disabled | Delete remote objects with no local source. Use only in an up-only workflow; see [Delete mode](#delete-mode). |
375
+ | `--up_cmd COMMAND` | `sync.up_cmd` | None | Run a local shell command after at least one successful upsync. |
376
+ | `--down_cmd COMMAND` | `sync.down_cmd` | None | Run a local shell command after at least one successful downsync. |
377
+ | `--cmd_timeout SECONDS` | `sync.cmd_timeout` | `30` | Timeout for each completion command. |
378
+ | `--error_email ADDRESS` | `sync.error_email` | None | Email collected warnings and errors through xyOps. |
379
+ | `--error_event EVENT_ID` | `sync.error_event` | None | Launch an Event with collected warnings and errors in its input data. |
380
+ | `--lock_file PATH` | `sync.lock_file` | Temporary file keyed by `base_url` | Use an explicit path for the built-in PID lock. |
381
+ | `--dry` | n/a | Off | Dry-Run Mode: Preview without writes, completion commands, or notifications. |
382
+ | `--verbose`, `-v` | n/a | Off | Show complete diffs and additional request, response, and write details. |
383
+ | `--quiet`, `-q` | n/a | Off | Suppress routine output and progress. Fatal errors remain visible, but collected sync warnings and errors can be suppressed. |
384
+
385
+ At least one direction must be enabled. The command is a single run, not a background watcher; repeat it through a scheduler or hook for automatic operation.
386
+
387
+ `--format json` and `--format jsonc` affect JSON values printed by shared helpers, such as API previews. `--raw` outputs raw IDs instead of friendly labels. See [Global CLI switches](../README.md#global-cli-switches).
388
+
389
+ ### Setup options
390
+
391
+ | Option | Behavior |
392
+ | --- | --- |
393
+ | `xy sync setup TYPES...` | Export the selected types to the current directory. Separate type names with spaces, or use `all`. |
394
+ | `--setup TYPES` | Alternative setup syntax, accepting comma-separated type names. For example, `xy sync --setup events,plugins`. |
395
+ | `--file_props PATHS` | Extract string properties into adjacent neighbor files. Accepts comma-separated dot paths or a JSON array. |
396
+ | `--stock` | Include stock definitions that ships wit xyOps (e.g. "Shell Plugin"), which setup normally omits. |
397
+ | `--marketplace` | Include Marketplace Plugins, which setup normally omits. |
398
+ | `--force` | Allow generated files to overwrite existing destinations. |
399
+ | `--dry` | Preview setup's output without creating folders or files. |
400
+
401
+ Workflow defaults in the `sync` configuration object are not used by setup. Pass setup choices explicitly on the command line. The common `lock_file` setting still applies so setup cannot overlap another sync operation for the same instance.
402
+
403
+ ## Two-way sync and modification times
404
+
405
+ Enable both directions for the same resource type:
406
+
407
+ ```sh
408
+ xy sync ./ --up events,plugins --down events,plugins --delete false --dry
409
+ ```
410
+
411
+ For each differing definition, the engine compares xyOps's `modified` timestamp with the local source's filesystem modification time. The effective local time is the newest timestamp among the JSON definition and all its external property files.
412
+
413
+ - If xyOps's timestamp is newer, the definition is pulled down.
414
+ - Otherwise, the local definition is pushed up. Equal timestamps favor the local source.
415
+ - If the definitions are identical after ignoring server-maintained metadata, nothing is written.
416
+
417
+ Two-way sync is experimental. Keep clocks synchronized, review dry runs, and preserve a history of both configuration and scripts. Git checkout, clone, restore, or file copying can give old content a new filesystem timestamp.
418
+
419
+ For automatic two-way operation, use a persistent tree and run every minute. Both directions are considered on each run; you do not need separate alternating up and down jobs. Newly created definitions still need a setup export or individual local source, and remotely deleted definitions leave their old source files behind until you remove them.
420
+
421
+ ## Remote-management warnings
422
+
423
+ Up-only sync records the managed definitions in xyOps's global sync state. The web interface and xyCLI update commands use this information to warn that a definition is under remote management and should not be edited directly.
424
+
425
+ When an applicable `xy TYPE update` command finds this flag, it displays a yellow warning and stops before sending the update. Add `--confirm` to override the warning for that command. This is an advisory safeguard rather than a permission lock, and it does not change the saved sync state. A later upsync can overwrite the confirmed change with the local definition.
426
+
427
+ Only types configured for upsync without downsync receive that warning. A down-only run clears the remote-management map; a two-way run omits its two-way definitions. Mixed-direction runs mark only their up-only definitions. Dry runs leave the map unchanged, and an identical map is not rewritten.
428
+
429
+ Each run publishes its own complete state map rather than merging it with earlier runs.
430
+
431
+ To clear every remote-management flag manually, run:
432
+
433
+ ```sh
434
+ xy system reset sync --confirm
435
+ ```
436
+
437
+ This resets only the global sync state map. It does not change xyOps definitions, local files, or saved sync settings. The next up-only sync publishes a new map for the definitions it manages.
438
+
439
+ Make sure only one sync instance talks to one xyOps installation.
440
+
441
+ ## Delete mode
442
+
443
+ Delete mode treats missing local definitions as a request to delete them **in xyOps**. Use it exclusively with an up-only source of truth. It is not a way to clean local files during downsync, and it does not provide two-way deletion propagation.
444
+
445
+ ### Prepare a complete inventory
446
+
447
+ The scanned directories must contain a source for **every other remote object of each type selected for deletion** that you intend to keep. Delete mode considers all eligible objects in the visible remote collection, not just objects previously synced or carrying a remote-management warning.
448
+
449
+ **Side-Note:** Any items marked as "stock" or "marketplace" in xyOps are omitted from deletion candidates.
450
+
451
+ Export a fresh inventory with an API Key that can see the entire selection:
452
+
453
+ ```sh
454
+ mkdir xyops-delete-inventory
455
+ cd xyops-delete-inventory
456
+ xy sync setup events plugins categories
457
+ ```
458
+
459
+ Verify that every object eligible for deletion that you intend to keep has a corresponding source, including workflows under the `events` selection. Stock and Marketplace objects may be omitted. Avoid filename collisions, omitted resources, missing mounts, and hidden source directories.
460
+
461
+ ### Preview a deliberate deletion
462
+
463
+ Remove the source file(s) for the Event you intend to delete, then preview from the complete inventory:
464
+
465
+ ```sh
466
+ xy sync ./ --up events,plugins,categories --down false --delete events --dry
467
+ ```
468
+
469
+ Inspect the entire deletion list and confirm that the directory and remote inventory are still complete. Apply only after that review:
470
+
471
+ ```sh
472
+ xy sync ./ --up events,plugins,categories --down false --delete events
473
+ ```
474
+
475
+ Deleting an Event does not automatically remove its dependencies. Remote API rules still apply, including restrictions on active jobs and references. Successful earlier updates or deletions are not rolled back if a later deletion fails.
476
+
477
+ An unavailable base directory stops the scan, but an existing empty directory can look like an empty inventory. Do not treat that guard as protection against an empty mount, accidental file removal, or missing exports of your own definitions.
478
+
479
+ Keep deletion disabled in general-purpose cron, Git hooks, and CI until you have a deliberate policy for maintaining and checking the complete inventory. Disable saved deletion for other runs with `--delete false`.
480
+
481
+ It is also highly recommended that you [keep daily backups of critical data](https://docs.xyops.io/hosting/daily-backups) before enabling sync, especially sync with delete.
482
+
483
+ ## Completion commands
484
+
485
+ Completion commands connect sync with your local workflow. For example, run a validation script after uploads, or commit changed files after downloads:
486
+
487
+ ```sh
488
+ xy sync ./ --up events,plugins --up_cmd "./after-upload.sh"
489
+ xy sync ./ --down events,plugins --down_cmd "git add . && git commit -m 'Sync from xyOps'"
490
+ ```
491
+
492
+ They run in a local shell, in the first specified base sync directory, using the CLI process's environment. Each command has its own timeout, defaulting to 30 seconds:
493
+
494
+ ```sh
495
+ xy sync ./ --down events,plugins --down_cmd "./commit-downloads.sh" --cmd_timeout 60
496
+ ```
497
+
498
+ ### When commands run
499
+
500
+ - `up_cmd` runs if at least one object was successfully upsynced (this includes deletes).
501
+ - `down_cmd` runs if at least one object was successfully downsynced.
502
+ - If both apply, `up_cmd` runs first, then `down_cmd`.
503
+ - Neither runs for an identical/no-op run, a scan that stopped before changes, or a dry run.
504
+ - A completion command can run after partial success even if another item failed. It is not proof that the whole run succeeded.
505
+
506
+ Command output is displayed when not quiet. Command failures are collected as sync errors; captured failed-command stdout and stderr may also print even in quiet mode. Notifications run after completion commands, so they can report those command failures too.
507
+
508
+ ### Commit downloads without recursive sync
509
+
510
+ For a dedicated automation repository, create `commit-downloads.sh` in its root:
511
+
512
+ ```sh
513
+ #!/bin/sh
514
+ set -eu
515
+
516
+ # Stage changes in this dedicated definition repository.
517
+ git add .
518
+
519
+ # A download need not produce a Git content change, so avoid empty commits.
520
+ if git diff --cached --quiet; then
521
+ exit 0
522
+ fi
523
+
524
+ # Skip Git hooks for this generated commit to avoid reentering sync.
525
+ git -c core.hooksPath=/dev/null commit -m "Sync from xyOps"
526
+ ```
527
+
528
+ Make it executable and configure it:
529
+
530
+ ```sh
531
+ chmod +x ./commit-downloads.sh
532
+ xy config --sync.down_cmd "./commit-downloads.sh"
533
+ ```
534
+
535
+ The repository needs a configured Git author identity. Keep unrelated changes and staged work out of this dedicated checkout; `git add .` includes everything below its current directory. For a shared repository, adjust the script to stage only your definition tree and avoid committing unrelated staged files.
536
+
537
+ The example commits locally. Add a push only when your automation account has the necessary credentials and you have chosen how those commits should enter the remote branch.
538
+
539
+ ## Error notifications
540
+
541
+ Use notifications to make unattended runs observable, particularly when using `--quiet`:
542
+
543
+ ```sh
544
+ xy sync ./ --up events,plugins --delete false \
545
+ --error_email ops@example.com --error_event SYNC_ERROR_EVENT_ID
546
+ ```
547
+
548
+ Both options respond to collected **warnings as well as errors**. A no-op run with no warnings sends nothing. Dry runs send no notifications, even if the scan reports problems.
549
+
550
+ ### Email reports
551
+
552
+ `error_email` calls xyOps's email API. The conductor must have working mail configuration, and the API Key needs the `send_emails` privilege.
553
+
554
+ The report includes warnings, errors, date, host, platform, CLI version, command line, and effective sync settings. Prefer CLI config files or environment variables for connection credentials rather than putting secrets directly in command arguments or completion commands that may appear in reports.
555
+
556
+ ### Error Events
557
+
558
+ `error_event` launches an existing Event by exact ID. Configure it with an enabled manual trigger and give the sync key permission to run it. It might notify a Channel, open a Ticket, or run your own recovery script.
559
+
560
+ The Event receives the following input data:
561
+
562
+ ```json
563
+ {
564
+ "input": {
565
+ "data": {
566
+ "errors": ["Example sync error"],
567
+ "warnings": ["Example source warning"]
568
+ }
569
+ }
570
+ }
571
+ ```
572
+
573
+ These arrays are available to the job as `input.data.errors` and `input.data.warnings`. Both can be empty individually, but at least one has entries when the notification Event is launched.
574
+
575
+ Keep the notification Event independent of the broken sync process. An Event that repeatedly invokes the same failing sync can create a notification loop.
576
+
577
+ ### What notifications cannot catch
578
+
579
+ Notifications use the same xyOps connection and API Key as sync. Missing credentials, startup failures, invalid direction settings, or an unreachable API may fail before the engine can collect and deliver a report. If email delivery fails, the current run aborts before attempting a subsequent `error_event` notification. Use external monitoring too when you need to detect failures of xyOps itself.
580
+
581
+ ### Exit status
582
+
583
+ Sync exits with status `1` if any sync warning or error occurs, including duplicate-source warnings, other scan warnings, and scan, API, file-write, tracking-state, or completion-command errors. This also applies in quiet mode and during a dry run. Setting the failure status does not interrupt the remaining completion commands or notifications; they can finish before the process exits. Fatal CLI errors also exit nonzero.
584
+
585
+ Runs with no warnings or errors exit with status `0`. Scan warnings prevent changes from being applied, so a warning-only run also reports failure. Both warnings and errors can trigger the configured notifications. Check the exit status in scripts, review reports, and verify remote definitions after critical deployments.
586
+
587
+ ## Built-in PID locking
588
+
589
+ Every `xy sync` command acquires a host-local PID lock before it loads remote definitions or touches local files. This includes ordinary up, down, two-way, delete, dry-run, and setup operations. The lock remains held through completion commands and error notifications, so every CLI entry point uses the same overlap protection automatically.
590
+
591
+ By default, xyCLI creates a lock file in the operating system's temporary directory. Its filename contains a hash of the normalized `base_url`, so syncs targeting the same xyOps instance on one host serialize even when they use different directories or API Keys. Syncs for different instances use different default locks.
592
+
593
+ When another live sync process owns the lock, the new command stops before doing any work, prints the owning PID and lock path, and exits nonzero. It does not wait or queue. Schedulers can try again on their next run. If a process was forcibly terminated and left its PID file behind, the next invocation detects that the PID is dead, removes the stale file, atomically acquires a replacement, and continues.
594
+
595
+ The lock contains its PID, start time, base URL, and a random ownership token. Normal completion and process-exit handlers remove it only when the token still matches, preventing an older process from deleting a newer lock.
596
+
597
+ ### Choose an explicit lock path
598
+
599
+ For a service account, set a stable absolute path in the CLI config:
600
+
601
+ ```json
602
+ {
603
+ "sync": {
604
+ "lock_file": "/run/xyops/xycli-sync.pid"
605
+ }
606
+ }
607
+ ```
608
+
609
+ Create the parent directory ahead of time and give the sync account permission to create and remove the file. You can override the saved path for one run:
610
+
611
+ ```sh
612
+ xy sync /srv/xyops-sync --up all --lock_file /run/xyops/xycli-sync.pid
613
+ ```
614
+
615
+ Prefer one service account for automatic sync. If multiple local accounts target the same instance, give them the same explicit path and ensure each can read the file and modify its parent directory. A PID lock coordinates processes only on one operating-system host. If multiple hosts can run sync against the same instance, arrange for only one host to do so or use a distributed scheduler; a shared PID file cannot reliably identify processes on another host.
616
+
617
+ ## Automatic sync with cron
618
+
619
+ Cron is a good fit for a persistent sync tree. Each invocation makes one pass and exits. Running every minute lets the next pass pick up changes made since the previous run. For automatic two-way sync, use a cron job from the account and host that own the persistent working tree.
620
+
621
+ ### Prepare the account and paths
622
+
623
+ Run the command manually as the same operating-system account that will run cron. That account needs its own CLI configuration, access to the tree, and a working Node.js installation. A download also needs write access. If `down_cmd` commits files, configure Git's author name and email for that account too.
624
+
625
+ Locate the executables:
626
+
627
+ ```sh
628
+ command -v xy
629
+ command -v node
630
+ ```
631
+
632
+ The examples below assume `xy` is `/usr/local/bin/xy` and Node.js is on the shown `PATH`. Adjust these for your installation. Node version managers often install outside this path. Even an absolute path to `xy` still needs a suitable `PATH` for its Node.js interpreter. The built-in PID lock supplies overlap protection, so no external locking command is required.
633
+
634
+ ### Up-only cron job
635
+
636
+ Use `crontab -e` for the intended account and add:
637
+
638
+ ```cron
639
+ PATH=/usr/local/bin:/usr/bin:/bin
640
+
641
+ # Send local changes to xyOps every minute.
642
+ * * * * * /usr/local/bin/xy sync /srv/xyops-sync --up events,plugins,categories --down false --delete false --quiet --error_email ops@example.com
643
+ ```
644
+
645
+ Choose only the item types your tree manages. This example explicitly disables downsync and deletion. Add `--error_event EVENT_ID` if you prefer an error-handling Event, or configure notifications in the account's CLI config file.
646
+
647
+ `--quiet` keeps routine progress and diffs out of cron's output, avoiding an email for every ordinary run. Fatal CLI errors still print to standard error. Collected sync warnings and errors can be suppressed, so quiet automation should have configured notifications and external monitoring. Remove `--quiet` while investigating a problem.
648
+
649
+ If the previous run is still active, the built-in PID lock rejects the overlapping invocation with a nonzero exit status. The next minute tries again. A single sync can therefore take longer than a minute without starting another copy.
650
+
651
+ ### Two-way cron job
652
+
653
+ For a persistent working tree where both sides may be edited, replace the previous entry with:
654
+
655
+ ```cron
656
+ PATH=/usr/local/bin:/usr/bin:/bin
657
+
658
+ # Every pass checks both directions. Never enable deletion here.
659
+ * * * * * /usr/local/bin/xy sync /srv/xyops-sync --up all --down all --delete false --quiet --error_email ops@example.com
660
+ ```
661
+
662
+ Each run compares the current local sources with xyOps and chooses a direction for each changed object using [modification times](#two-way-sync-and-modification-times). `all` still operates only on definitions with existing local sources. It does not discover new objects.
663
+
664
+ Configure a `down_cmd`, such as the [commit-downloads script](#commit-downloads-without-recursive-sync), to record web-interface changes in Git after downloads. Use a persistent clone, synchronized clocks, and one writer at a time. Avoid periodically replacing this tree with a fresh Git checkout, which changes timestamps and can change conflict decisions.
665
+
666
+ These examples use a **user crontab**, with five time fields followed by the command. `/etc/crontab` and `/etc/cron.d` entries need an additional account field. Cron has a limited environment; see the [crontab manual](https://man7.org/linux/man-pages/man5/crontab.5.html) for its environment and syntax.
667
+
668
+ ## Automatic upsync with Git hooks
669
+
670
+ Git hooks let local repository operations trigger a sync operation on certain actions. They execute on the machine performing the command, using that person's CLI credentials. Hooks are not automatically installed when somebody clones a repository.
671
+
672
+ The examples assume definitions live in an `automation/` subdirectory of the repository. They deploy existing definitions from the **working tree**, not Git's staging area or an arbitrary commit. Keep that directory clean before deploying so uncommitted edits do not become part of an ostensibly committed release.
673
+
674
+ Create a tracked hooks directory and enable it in each clone that should deploy:
675
+
676
+ ```sh
677
+ mkdir -p .githooks
678
+ git config core.hooksPath .githooks
679
+ ```
680
+
681
+ Save the selected script under the hook filename shown below and make it executable. Choose a deployment trigger rather than installing every example at once. The built-in PID lock prevents these hooks from overlapping another local sync for the same xyOps instance.
682
+
683
+ All examples disable saved deletion, downloads, and completion commands. Disabling completion commands avoids a sync command launching another Git operation that triggers the same deployment hook. Credentials belong in the user's CLI config or environment, not a tracked hook script.
684
+
685
+ ### Deploy after a commit on the main branch
686
+
687
+ Save as `.githooks/post-commit`:
688
+
689
+ ```sh
690
+ #!/bin/sh
691
+ set -eu
692
+
693
+ # Only this clone's main branch deploys.
694
+ [ "$(git symbolic-ref --quiet --short HEAD)" = main ] || exit 0
695
+ repo_root=$(git rev-parse --show-toplevel)
696
+ cd "$repo_root"
697
+
698
+ # A partial commit must not deploy leftover working-tree changes.
699
+ if [ -n "$(git status --porcelain -- automation)" ]; then
700
+ echo "Sync skipped: automation/ has uncommitted changes." >&2
701
+ exit 1
702
+ fi
703
+
704
+ xy sync "$repo_root/automation" --up events,plugins,categories \
705
+ --down false --delete false --up_cmd false --down_cmd false \
706
+ --error_email ops@example.com
707
+ ```
708
+
709
+ ```sh
710
+ chmod +x .githooks/post-commit
711
+ ```
712
+
713
+ This runs after each commit on `main`, including commits that do not change automation; identical definitions are skipped. A post-commit hook cannot undo the commit if deployment fails. Check the output and notification reports before considering the deployment complete.
714
+
715
+ ### Deploy before pushing the main branch
716
+
717
+ Use `.githooks/pre-push` when you want a push to trigger sync. Git passes proposed ref updates on standard input:
718
+
719
+ ```sh
720
+ #!/bin/sh
721
+ set -eu
722
+
723
+ deploy=false
724
+ while read -r local_ref local_oid remote_ref remote_oid; do
725
+ # Ignore deletions and unrelated branches.
726
+ if [ "$remote_ref" = refs/heads/main ] && [ "$local_ref" != '(delete)' ]; then
727
+ # Sync reads the checked-out tree, so require the pushed commit to be HEAD.
728
+ if [ "$local_oid" != "$(git rev-parse HEAD)" ]; then
729
+ echo "Check out the commit being pushed before syncing." >&2
730
+ exit 1
731
+ fi
732
+ deploy=true
733
+ fi
734
+ done
735
+
736
+ [ "$deploy" = true ] || exit 0
737
+ repo_root=$(git rev-parse --show-toplevel)
738
+ cd "$repo_root"
739
+ if [ -n "$(git status --porcelain -- automation)" ]; then
740
+ echo "Sync skipped: automation/ has uncommitted changes." >&2
741
+ exit 1
742
+ fi
743
+
744
+ xy sync "$repo_root/automation" --up events,plugins,categories \
745
+ --down false --delete false --up_cmd false --down_cmd false \
746
+ --error_email ops@example.com
747
+ ```
748
+
749
+ Make sure to make the hook script executable:
750
+
751
+ ```sh
752
+ chmod +x .githooks/pre-push
753
+ ```
754
+
755
+ This deployment happens **before** Git sends the push. A later rejected push does not roll back xyOps changes. A sync warning or error makes the hook exit nonzero and aborts the push. Review notification reports too. See [Exit status](#exit-status).
756
+
757
+ ### Deploy when pushing a release tag
758
+
759
+ Git has no separate tag-push hook. Use `pre-push` and inspect the refs. To deploy only tags named `v*`, replace the preceding hook with:
760
+
761
+ ```sh
762
+ #!/bin/sh
763
+ set -eu
764
+
765
+ deploy=false
766
+ while read -r local_ref local_oid remote_ref remote_oid; do
767
+ [ "$local_ref" != '(delete)' ] || continue
768
+ case "$remote_ref" in
769
+ refs/tags/v*)
770
+ # Peel annotated tags and require their commit to be checked out.
771
+ tag_commit=$(git rev-parse "$local_oid^{commit}")
772
+ if [ "$tag_commit" != "$(git rev-parse HEAD)" ]; then
773
+ echo "Check out the tagged commit before syncing." >&2
774
+ exit 1
775
+ fi
776
+ deploy=true
777
+ ;;
778
+ esac
779
+ done
780
+
781
+ [ "$deploy" = true ] || exit 0
782
+ repo_root=$(git rev-parse --show-toplevel)
783
+ cd "$repo_root"
784
+ if [ -n "$(git status --porcelain -- automation)" ]; then
785
+ echo "Sync skipped: automation/ has uncommitted changes." >&2
786
+ exit 1
787
+ fi
788
+
789
+ xy sync "$repo_root/automation" --up events,plugins,categories \
790
+ --down false --delete false --up_cmd false --down_cmd false \
791
+ --error_email ops@example.com
792
+ ```
793
+
794
+ Multiple matching tags pushed together cause one sync of the checked-out tree. If the release should deploy only after the remote repository accepts the tag, use the GitHub Actions workflow below instead.
795
+
796
+ For hook arguments, execution context, and `core.hooksPath`, see the [Git hooks reference](https://git-scm.com/docs/githooks).
797
+
798
+ ## Automatic upsync with GitHub Actions
799
+
800
+ GitHub Actions can deploy a committed tree from a clean checkout. Use up-only sync: timestamps in a fresh runner checkout should not decide two-way conflicts.
801
+
802
+ ### Add repository secrets
803
+
804
+ In the repository, open **Settings > Secrets and variables > Actions** and add these repository secrets:
805
+
806
+ | Secret | Value |
807
+ | --- | --- |
808
+ | `XYOPS_BASE_URL` | Your xyOps base URL, such as `https://xyops.example.com`. |
809
+ | `XYOPS_API_KEY` | An API Key with the required read, edit, and `update_state` privileges. |
810
+
811
+ The runner needs network access to xyOps. For a private endpoint, use a suitably connected self-hosted runner or your established private-network access method. See GitHub's [repository secrets guide](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets).
812
+
813
+ ### Deploy pushes to main
814
+
815
+ Save this as `.github/workflows/xyops-sync.yml`:
816
+
817
+ ```yaml
818
+ name: Sync xyOps automation
819
+
820
+ on:
821
+ push:
822
+ branches: [main]
823
+ paths:
824
+ - 'automation/**'
825
+ - '.github/workflows/xyops-sync.yml'
826
+ workflow_dispatch:
827
+
828
+ permissions:
829
+ contents: read
830
+
831
+ # Serialize deployments from this repository to this xyOps environment.
832
+ concurrency:
833
+ group: xyops-production-sync
834
+ cancel-in-progress: false
835
+
836
+ jobs:
837
+ sync:
838
+ runs-on: ubuntu-latest
839
+ env:
840
+ XYOPS_BASE_URL: ${{ secrets.XYOPS_BASE_URL }}
841
+ XYOPS_API_KEY: ${{ secrets.XYOPS_API_KEY }}
842
+ steps:
843
+ - name: Check out the committed definitions
844
+ uses: actions/checkout@v7
845
+
846
+ - name: Set up Node.js
847
+ uses: actions/setup-node@v7
848
+ with:
849
+ node-version: '24'
850
+
851
+ - name: Install xyCLI
852
+ run: npm install --global @pixlcore/xycli
853
+
854
+ - name: Check connection settings
855
+ run: |
856
+ : "${XYOPS_BASE_URL:?Set the XYOPS_BASE_URL repository secret}"
857
+ : "${XYOPS_API_KEY:?Set the XYOPS_API_KEY repository secret}"
858
+
859
+ - name: Apply changes
860
+ run: |
861
+ xy sync "$GITHUB_WORKSPACE/automation" \
862
+ --up events,plugins,categories --down false --delete false \
863
+ --up_cmd false --down_cmd false --error_email "ops-oncall@yourcompany.com"
864
+ ```
865
+
866
+ Choose resource types that match your tree. Pin the xyCLI installation to a version you have validated, for example `npm install --global @pixlcore/xycli@YOUR_TESTED_VERSION`, when you want reproducible deployments. The examples use the official [checkout](https://github.com/actions/checkout) and [setup-node](https://github.com/actions/setup-node) actions; use versions compatible with your runner.
867
+
868
+ Leave routine output enabled in CI so its logs contain diffs and progress. Sync warnings and errors produce a nonzero exit status and fail the workflow step. Review notifications and logs to resolve the problem, and verify critical definitions after deployment.
869
+
870
+ ### Deploy accepted release tags instead
871
+
872
+ To trigger the same job only when GitHub accepts a release tag, replace the workflow's `on` section with:
873
+
874
+ ```yaml
875
+ on:
876
+ push:
877
+ tags: ['v*']
878
+ workflow_dispatch:
879
+ ```
880
+
881
+ The checkout action checks out the triggering tag. This workflow does not require the person pushing it to have CLI credentials locally. Choose branch or tag deployment deliberately; enabling both can deploy the same release twice.
882
+
883
+ Repository concurrency controls these workflow runs. Coordinate all writers to the same environment. See [GitHub Actions workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) for triggers, permissions, and concurrency.
884
+
885
+ ## Troubleshooting and operational limits
886
+
887
+ | Symptom | What to check |
888
+ | --- | --- |
889
+ | Sync says to enable a direction | Set `--up`, `--down`, or saved defaults. Setup and ordinary sync are different operations. |
890
+ | The wrong directory is scanned | Positional paths take precedence, followed by `base_dirs`, then the current directory. Check your command and config; use absolute paths in automation. |
891
+ | A config override seems to vanish | The user file replaces a system `sync` object as a whole. Environment settings take precedence over files. See [configuration](#configuration-and-option-reference). |
892
+ | An object is missing from the local tree | Ordinary downsync does not discover it. Export that object or run setup in a separate staging directory. |
893
+ | A source cannot find its remote object | Match its exact `type` and `data.id` to an existing visible object in this xyOps installation. Create/import missing objects separately. |
894
+ | A duplicate-source warning stops sync | Keep one file per resource type and ID, even when the copies are identical. Remove duplicates and avoid overlapping base directories, then retry. |
895
+ | One bad file stops unrelated updates | The scan validates all JSON sources before applying changes. Fix malformed JSON, multi-item XYPDF, unsupported types, missing objects, or unreadable property files. |
896
+ | A script edit has no effect | Check the matching JSON stem, property suffix, filename extension, and property path. Hidden paths are skipped. |
897
+ | Two-way sync overwrites an unexpected side | Compare the remote modification time with the newest JSON/property-file mtime. Fresh checkouts, clock skew, or touched files can make local sources appear newer. |
898
+ | An Event or workflow does not execute | Sync saves definitions; it does not run them. Use `xy run EVENT_ID` separately. |
899
+ | `down_cmd` does not run | It requires at least one successful download. A no-op, dry run, scan failure, or deletion alone does not trigger it. |
900
+ | A download commit launches another sync | Disable deployment Git hooks for commits made by `down_cmd`. The nested sync will otherwise be rejected by the built-in PID lock. |
901
+ | Sync reports another PID is running | Another local sync owns the lock. Let it finish. If the PID is no longer alive, the next run recovers the stale file automatically. If the PID belongs to an unrelated recycled process, verify that no sync is active before removing the reported lock file. |
902
+ | A scheduled run prints nothing | `--quiet` suppresses routine output and collected reports. Remove it to investigate and configure notifications. |
903
+ | Sync exits nonzero after a warning report | Scan warnings prevent changes from being applied, so warnings and errors both exit with status `1`. Resolve the reported problem before retrying. |
904
+ | Notifications never arrive | Check API access, email setup and privileges, the error Event's manual trigger, and failures occurring before reporting. |
905
+ | API Key updates behave differently after import | Portable definitions do not transfer usable secret key material. Treat key credentials separately from their definitions. |
906
+
907
+ ### Plan for partial success
908
+
909
+ Sync is not a transaction. A scan warning prevents the apply phase, but an API or filesystem failure during application can leave earlier changes in place. Completion commands may run for those successful changes. Individual file writes are atomic, but a definition and its external property files are not a single atomic group.
910
+
911
+ Before important deployments, review a dry run, retain recoverable versions of both sides, and coordinate writers. After a failure, fix the reported problem, inspect both sides, and run another preview before retrying.
912
+
913
+ ### Keep the inventory deliberate
914
+
915
+ Sync uses IDs rather than titles or filenames, and does not remap IDs for a different xyOps installation. Duplicate sources for the same type and ID generate warnings and stop the run before changes; keep one source per object and avoid overlapping base directories. Split inventories by ownership when useful, but remember that each run replaces the shared up-only remote-management tracking map rather than combining independent jobs' maps.
916
+
917
+ New remote objects, objects removed remotely, filename changes, and dependency ordering need explicit maintenance. Deletion remains an up-only workflow with a complete inventory. Two-way sync remains experimental and resolves whole objects by modification time, without a merge or conflict prompt.
918
+
919
+ - **Command reference:** [Sync](help.md#sync), [Sync setup](help.md#sync-setup), [Exports](help.md#export), [Imports](help.md#import), and [Run](help.md#run).
920
+ - **Learn more about xyOps:** [xyOps Portable Data Format](https://docs.xyops.io/xypdf) and [xyOps API Keys](https://docs.xyops.io/api#api-keys).