@openengio/cli 0.27.1 → 0.36.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.
package/README.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @openengio/cli
2
2
 
3
+ ## Link review and impact
4
+
5
+ `openeng links status <object> --project <id> --ref <ref>` prints the stable head, tracked
6
+ links, review and bounds states, clearing attribution and aggregate bounds. Review is `cleared`,
7
+ `suspect`, `unreviewed`, `not-visible` or `too-many-links`; a significant upstream change makes
8
+ a cleared link suspect. Bounds independently answer `in`, `out`, `none`, `no-value`,
9
+ `unit-mismatch` or `not-visible`. Aggregates carry coverage and hidden or missing counts.
10
+ Numeric bounds include their units. Text output from `objects-sdk readObject` also shows
11
+ property values with units, using one additional catalogue read; `--json` retains the wire result.
12
+
13
+ `openeng links clear <link> --version <v> --project <id> --ref <ref>` clears after review.
14
+ For 1 to 100 links, list the ids and repeat `--version` once per link in matching order, for
15
+ example `openeng links clear <link1> <link2> --version <v1> --version <v2> --project <id>
16
+ --ref <ref>`. Each opaque upstream version comes from status and is a precondition; the clear is
17
+ atomic. `upstream-changed` requires reading and reviewing again; `upstream-not-readable` requires
18
+ upstream read access. Clearing records the person and time and leaves bounds unchanged. The edge's
19
+ write rule governs clearing, with no organisation setting. User, run-apply, client and service
20
+ tokens are admitted; run-explore and run-types are refused at admission. There is no object-agent
21
+ clearing tool. An unknown outcome exits 5; recover by correlation before repeating a clear.
22
+
23
+ `openeng branches impact <ref> --base <base> --project <id>` prints review, bounds and aggregate
24
+ changes against the merge base, plus downstream reach to depth three and hidden counts. Both heads
25
+ are pinned. Added and removed states identify new and deleted links; `head-moved` and `over-bound`
26
+ refuse without partial output. All three commands support `--json` for the complete answer and
27
+ print refusals by name.
28
+
3
29
  `openeng` is the platform's command-line client. It signs a person in through the browser, stores the
4
30
  session, and calls the platform's host. It runs on Node 22 and has no argument-parser dependency
5
31
  (`node:util`'s `parseArgs` serves because every command takes the same few flags).
@@ -41,7 +67,7 @@ The installed package contains the bundled command and the skill. It carries no
41
67
  | `openeng org current` | prints the organisation the stored session is attached to, or none (`{ "organisation": null }` under `--json`); sends no request | none |
42
68
  | `openeng projects list` | lists the projects the signed-in person may list | one |
43
69
  | `openeng demo seed --project <id> [--size standard\|full]` | plays a generic open-cycle gas turbine plant into a project the person may list; `standard` is the default size, and a repeat completes what is left | one listing, one catalogue read, four to publish the types when they are not yet published, then one per object and edge |
44
- | `openeng link <explore\|branch-diff\|project> --project <id> --ref <ref>`, `openeng link <ontology\|change-set> --ref <ref>`, `openeng link <object\|type\|proposal\|app> <identifier> --project <id> --ref <ref>`, `openeng link app-page <appKey> <pageKey> --project <id> --ref <ref>`, `openeng link app-draft <appKey> --ref <ref>`, `openeng link organisation-page <page>` | prints the address of a page of the web application: the host's web origin joined to the page's path, built for the attached organisation; `--json` answers `{ link }`; a flag the page does not read or a ref outside `main` and `<projectId>/<branch>` is refused (exit 2) before any call; a branch must match `--project` when given. The command reads the ref and the page's object, catalogue type, app page or proposal before printing, and refuses a missing subject with no address; a host with no web origin is refused too. `openeng link --help` prints every form | one `readWebOrigin`, plus a ref read for a branch or a project page on main, plus the subject read for object, type, app, app-page or proposal; the reads run in parallel |
70
+ | `openeng link <branch-diff\|project> --project <id> --ref <ref>`, `openeng link explore --project <id> --ref <ref> (--object <id> \| --object-set <id>)`, `openeng link <ontology\|change-set> --ref <ref>`, `openeng link <object\|type\|proposal\|app> <identifier> --project <id> --ref <ref>`, `openeng link app-page <appKey> <pageKey> --project <id> --ref <ref>`, `openeng link app-draft <appKey> --ref <ref>`, `openeng link organisation-page <page>` | prints the address of a page of the web application: the host's web origin joined to the page's path, built for the attached organisation; `--json` answers `{ link }`; a flag the page does not read or a branch or baseline outside `main` and `<projectId>/<branch>` is refused (exit 2) before any call; a branch must match `--project` when given. The command reads the branch or baseline and the page's object, catalogue type, app page or proposal before printing, and refuses a missing subject with no address; a host with no web origin is refused too. `openeng link --help` prints every form | one `readWebOrigin`, plus a branch or baseline read for a branch or a project page on main, plus the subject read for object, type, app, app-page, proposal or Explore’s seed; the reads run in parallel |
45
71
  | `openeng types draft open` | opens a change set on the attached organisation's namespace | one |
46
72
  | `openeng projects create --name <text>` | creates a project in the organisation the session is attached to, under a minted idempotency key | one |
47
73
  | `openeng types list --project <id>` | lists the project's published types | one |
@@ -51,26 +77,86 @@ The installed package contains the bundled command and the skill. It carries no
51
77
  | `openeng types draft state --change-set <id>` | whether the change set is open, accepted or discarded | one |
52
78
  | `openeng types draft revise --change-set <id> --revision <token> --from <file> [--rail-groups <json>]` | replaces the change set's delta with the JSON object in the file, keyed by type key; a type's rail placement is `presentationBindings.rail` inside its document; `--rail-groups` carries the organisation's whole list of rail groups as a JSON array of strings, and without it the change set keeps the groups its last revision held; a file that is unreadable, not JSON or not an object, or a flag that is not a JSON array of strings, is refused (exit 2) before any call; the host validates the delta | one |
53
79
  | `openeng types draft validate --change-set <id> --revision <token>` | the host's violations for that revision, printed with their count; exits 4 when there is one or more and 0 when there are none, so a script can gate `accept` on the exit code | one |
80
+ | `openeng types draft try --type <key> --sample <file> [--change-set <id>]` | checks one sample instance, a JSON object of raw property values in the file, against the open change set's version of the type, with the check a write runs; it reads the open change set (the attached organisation's, or the one `--change-set` names) and writes nothing; exits 0 with `{ valid: true, violations: [] }` when the sample fits, and 4 with each violation's path and rule on standard output when it does not; a file that is unreadable, not JSON or not an object, no open change set, or a type key the change set does not hold or that names a verb type, is refused (exit 2) | two reads, one with `--change-set` |
54
81
  | `openeng types draft discard --change-set <id>` | discards the change set | one |
55
82
  | `openeng types draft accept --change-set <id> --revision <token>` | publishes the change set's types at the revision the person reviewed, under a minted idempotency key | one |
56
- | `openeng rules add` | adds a project or organisation ref rule | one |
83
+ | `openeng attachment add <object> <property> <file> --project <id> --ref <ref>` | stages, saves and prints the descriptor | four |
84
+ | `openeng attachment get <object> <property> [--out <path>] --project <id> --ref <ref>` | downloads and prints the written path | one |
85
+ | `openeng attachment list <object> --project <id> --ref <ref>` | prints attachment properties and descriptors | two |
86
+ | `openeng hosted-app publish <bundle> --project <id> --key <app key>` | publishes the bundle file as the next version of the project's hosted app under the key, creating the app when the project has none; prints the version, its SHA-256 and size, and the one-wall warning for an app that holds the one-wall permission | three to four |
87
+ | `openeng hosted-app list --project <id>` | lists the project's hosted apps | one |
88
+ | `openeng hosted-app versions <app key> --project <id>` | lists the app's versions, newest first | two |
89
+ | `openeng hosted-app revoke <app key \| app id \| version id> --project <id>` | revokes one version (an argument starting `hav_`) or the whole app by its key or an `app_` id; the next call under any launch of it is refused | one for an id, or two for an app key |
90
+ | `openeng rules add` | adds a project or organisation branch rule | one |
57
91
  | `openeng rules list` | lists the selected project's or organisation's rules | one |
58
92
  | `openeng rules remove` | removes a rule by identifier | one |
59
- | `openeng org-refs create` | creates an organisation ref, optionally read-only | one |
60
- | `openeng org-refs list` | lists visible organisation refs | one |
61
- | `openeng org-refs delete` | deletes a writable organisation ref | one |
62
- | `openeng org-refs mark-read-only` | marks an organisation ref read-only | one |
63
- | `openeng locks set` | sets or lifts an object's lock on a project ref | one |
93
+ | `openeng org-refs create` | creates an organisation branch or baseline, optionally read-only | one |
94
+ | `openeng org-refs list` | lists visible organisation branches and baselines | one |
95
+ | `openeng org-refs delete` | deletes a writable organisation branch | one |
96
+ | `openeng org-refs mark-read-only` | marks an organisation branch or baseline read-only | one |
97
+ | `openeng locks set` | sets or lifts an object's lock on a project branch | one |
64
98
  | `openeng ceiling show` | reads a project's maximum agent role | one |
65
99
  | `openeng ceiling set` | sets a project's maximum agent role | one |
66
100
  | `openeng schema-update request` | records a schema update request | one |
67
101
  | `openeng schema-update show` | reads the latest visible request, or no request | one |
68
102
  | `openeng skill [<topic> \| playbook <name>]` | prints the main skill and lists topics, or prints one bundled topic; needs no host and no session | none |
69
103
 
104
+ Attachments are top-level properties declared with kind `attachment`. Accepted file classes are
105
+ `image` (PNG, JPEG, WebP, GIF), `pdf`, `step` (STEP), `mesh` (STL) and `text` (plain text and the
106
+ admitted text-file extensions). Each body is limited to 26,214,400 bytes (25 MiB).
107
+
108
+ The hosted-app commands take `--project`, and `publish` also takes `--key`, an app key: lower-case
109
+ letters, digits and hyphens, at most 63 characters. The parser refuses any other value (exit 2), so
110
+ the flag cannot carry a token. `publish` reads the bundle file whole; the host bounds it at 4 MiB.
111
+ A refused publish, such as one by a person who may not publish into the project, prints the host's
112
+ refusal and exits 4. An app that holds the one-wall permission makes `publish` print the one-wall
113
+ warning after the version line, and `--json` carries `oneWall` and `warning` beside the version. A publisher who may not read the
114
+ organisation's one-wall permissions still publishes: the command adds one line saying this account
115
+ cannot read whether the app may send data out of the browser, and `--json` carries `"oneWall": "unknown"`.
116
+
117
+ All three attachment commands require explicit `--project` and `--ref` and an attached organisation.
118
+ `add` uses the local file's basename, stages it, reads the object and catalogue, then saves; it prints
119
+ the descriptor only after a succeeded save. `add` and `list` use the catalogue's latest version of
120
+ the object's type key, even when the object pins an older version. `get` verifies the downloaded
121
+ SHA-256 before writing, uses the descriptor's name when `--out` is absent, and overwrites an existing
122
+ destination. `list` prints one `{ property, descriptor }` JSON object per present attachment in text
123
+ mode, or `no attachments` when empty; `--json` returns `{ scope, value: [...] }`.
124
+ `get --json` returns `{ path }` with scope metadata; `add --json` returns the descriptor with it.
125
+
126
+ The common exit codes apply: 0 for success, 2 for local usage refusals, 3 for authentication or a
127
+ moved session, 4 for a platform refusal, 5 for an unknown save outcome, 6 for invalid requests,
128
+ missing subjects or exceeded bounds, and 7 for host, transport, file I/O or integrity failures.
129
+ An unknown save outcome prints its correlation and recovery trailer instead of a descriptor.
130
+
70
131
  An object type's `subscriptionRoles` field declares which roles it admits: owner, approver or watcher.
71
132
  Owner is limited to one; approver and watcher may have optional maxima. Without the field, only
72
133
  watchers are admitted.
73
134
 
135
+ ## Link destinations
136
+
137
+ | kind | destination |
138
+ |---|---|
139
+ | `object <id>` | The object's type page with the object open; the type comes from its read. |
140
+ | `type <key>` | The type's page. |
141
+ | `explore` | Explore with exactly one `--object <id>` or `--object-set <id>` seed. |
142
+ | `branch-diff` | The branch comparison page. |
143
+ | `project` | The project page. |
144
+ | `proposal <id>` | The proposal review page. |
145
+ | `ontology` | The organisation's Types workspace. |
146
+ | `change-set` | The organisation's Change set workspace. |
147
+ | `app <key>` | The published app's first page. |
148
+ | `app-page <appKey> <pageKey>` | The named page of a published app. |
149
+ | `app-draft <key>` | The app's draft screen in the Types workspace. |
150
+ | `organisation-page <page>` | The named organisation administration page or settings. |
151
+
152
+ `explore` is the only kind that opens Explore. Giving neither seed flag or both refuses before a
153
+ host call (exit 2). The seed flags are only for `explore`. The other forms and their scope flags are in
154
+ `openeng link --help`.
155
+
156
+ An object link reads the object once and takes its type key from that answer. Explore reads its
157
+ selected object or saved set before printing the link. The subject, ref and web-origin reads run
158
+ in parallel; a refused read prints no link.
159
+
74
160
  ## The rules commands
75
161
 
76
162
  These commands use the Admin SDK. Choose exactly one of `--project <id>` and `--organisation`.
@@ -95,7 +181,7 @@ and `projectId: null`; project rules keep `scope: "project"`. That scope stays i
95
181
  Missing flags or a consequence other than the two spellings above refuse before any host call (exit 2).
96
182
  The host validates the pattern.
97
183
 
98
- A project rule's pattern is `main` or a ref under that project's identifier, exactly or ending
184
+ A project rule's pattern is `main` or a branch or baseline under that project's identifier, exactly or ending
99
185
  in `/*` to match the prefix. A matching `merge-only` rule requires changes through a branch and
100
186
  merge. A `locked-rows-only` rule binds merge-only row locks while leaving unlocked rows direct.
101
187
  Adding or removing rules needs a person's `administer projects` capability on the selected
@@ -105,30 +191,30 @@ capability; read that help line to find what the person needs. A change takes ef
105
191
  request. A `protected` write refusal means to use the branch and review workflow.
106
192
  Agents must ask a person to manage rules.
107
193
 
108
- ## Organisation refs, locks, agent ceilings and schema requests
194
+ ## Organisation branches and baselines, locks, agent ceilings and schema requests
109
195
 
110
196
  All these commands need an attached organisation, make one host call (plus a refresh when needed),
111
197
  and send no idempotency key. Text output is described below; `--json` always returns `{ scope, value }`
112
198
  with the full route response in `value`, including null. A host refusal uses the standard explanation
113
- and next step. Changing rules, refs and locks is a person's action, never a way for an agent to bypass
199
+ and next step. Changing rules, branches and baselines and locks is a person's action, never a way for an agent to bypass
114
200
  protection.
115
201
 
116
- Creating, deleting or marking organisation refs needs `administer projects` in the attached
202
+ Creating, deleting or marking organisation branches and baselines needs `administer projects` in the attached
117
203
  organisation. Setting a ceiling needs `administer projects` on the selected project; setting
118
204
  or lifting a lock needs `lock objects` there. Requesting a schema update needs `write ref` on
119
- a branch's home project, or `administer projects` for an organisation ref. Rule and organisation-ref
205
+ a branch's home project, or `administer projects` for an organisation branch or baseline. Rule and organisation branch or baseline
120
206
  lists and ceiling reads need `read`. Schema-update show reads only the caller's own requests in the
121
207
  token's organisation, without requiring project read access. Each command's `--help` names its requirement.
122
208
  A branch whose name contains `/` cannot request or show a schema update today.
123
209
 
124
210
  - `openeng org-refs create --name <name> [--read-only]` calls `createOrganisationRef` and prints
125
211
  `ref <name> read-only` or `ref <name> writable`. It creates from the organisation's trunk head.
126
- The name is one non-reserved ref segment, such as `release-v1`; without the flag it is writable.
127
- - `openeng org-refs list` calls `listOrganisationRefs` and prints each ref in that form, or `no refs`.
212
+ The name is one non-reserved name segment, such as `release-v1`; without the flag it is writable.
213
+ - `openeng org-refs list` calls `listOrganisationRefs` and prints each branch or baseline in that form, or `no refs`.
128
214
  - `openeng org-refs delete --name <name>` calls `deleteOrganisationRef` and prints `deleted ref <name>`.
129
- A read-only ref cannot be deleted.
215
+ A baseline cannot be deleted.
130
216
  - `openeng org-refs mark-read-only --name <name>` calls `markOrganisationRefReadOnly` and prints the
131
- ref's name and read-only state. All four organisation-ref calls have no selector.
217
+ branch or baseline's name and read-only state. All four organisation branch or baseline calls have no selector.
132
218
  - `openeng locks set --project <id> --ref <ref> --object <objectId> --lock <none|merge-only|frozen>`
133
219
  calls the Objects client's `setObjectLock` with selector `{ projectId, ref }` and input
134
220
  `{ objectId, lock }`. It prints `object <objectId> lock <lock>`. `none` lifts the lock;
@@ -140,11 +226,11 @@ A branch whose name contains `/` cannot request or show a schema update today.
140
226
  and prints `schema update <ref> <state>`. It records a request; execution is separate.
141
227
  - `openeng schema-update show --ref <ref>` calls `readSchemaUpdateRequest` and prints that same
142
228
  form, or `no request` for null. Schema calls have no selector and act in the attached organisation.
143
- The ref is a project branch or a bare organisation ref name; `main` is refused by the host.
229
+ The branch or baseline is a project branch or a bare organisation branch or baseline name; `main` is refused by the host.
144
230
 
145
231
  Missing required flags and unknown lock or ceiling spellings refuse before any call (exit 2).
146
- For an uncertain outcome, rules, organisation refs, ceiling and schema requests name a read-only
147
- command: list the rules or organisation refs, or show the ceiling or schema request. Inspect that
232
+ For an uncertain outcome, rules, organisation branches and baselines, ceiling and schema requests name a read-only
233
+ command: list the rules or organisation branches and baselines, or show the ceiling or schema request. Inspect that
148
234
  answer before acting again. The lock command instead names the same idempotent `openeng locks set`
149
235
  command with the same arguments, which sets the same lock again.
150
236
 
@@ -216,7 +302,7 @@ reach; the stored organisation decides only what the CLI prints and whether it r
216
302
  failure the host did not answer carries no scope. A command with no scope has no `scope` field.
217
303
  - **The hint.** Every `not-found` the host answers to a gated command prints, after the refusal,
218
304
  "not found in organisation <name>: check that you are attached to the correct organisation (openeng org
219
- current)", and one line more when the command named a ref: `ref forms: the trunk is the bare "main";
305
+ current)", and one line more when the command named a branch or baseline: `ref forms: the trunk is the bare "main";
220
306
  a branch is "<project id>/<name>"`. The hint goes to standard error with or without `--json`.
221
307
 
222
308
  A command that reaches the host with a session whose access token has expired sends one `refresh`
@@ -305,7 +391,7 @@ for, built when the process starts from those entries (`OBJECTS_ROUTE_ENTRIES`,
305
391
  Every command takes `--json`, which prints one JSON value on standard output in place of text.
306
392
  The flags a command reads, named in its usage above, are `--project`, `--pattern`, `--consequence`,
307
393
  `--rule-id`, `--name`, `--change-set`, `--revision`, `--from`, `--size`, `--idempotency-key`,
308
- `--rail-groups`, and, for
394
+ `--rail-groups`, `--key` (for `openeng hosted-app publish`), and, for
309
395
  `openeng connector create`, `--system`, `--stack`, `--company`, `--team`, `--owner` and `--label`; a
310
396
  command that needs one it was not given refuses before any call (exit 2).
311
397
 
@@ -315,9 +401,17 @@ command that needs one it was not given refuses before any call (exit 2).
315
401
  | `OPENENG_SIGN_IN_ORIGIN` | `--sign-in-origin` | for a `unix:` host, the network host's `http://127.0.0.1:<port>` or `http://[::1]:<port>` origin. Only the browser visits it; no credential is sent to it. Any non-loopback origin is refused. The flag wins. |
316
402
  | `OPENENG_SESSION_STORE` | none | `file` selects the file store. Unset selects the platform's credential store where its tool answers, else the file store. Any other value is refused. |
317
403
  | `XDG_CONFIG_HOME` | none | the configuration directory when it is an absolute path, else `~/.config`. |
404
+ | `OPENENG_NO_UPDATE_CHECK` | none | set to `1` to disable the daily npm update check and notice. |
405
+ | `CI` | none | when set (even empty), disables the update check and notice. |
318
406
 
319
407
  `openeng` reads no other environment variable.
320
408
 
409
+ After printing a command's result, `openeng` checks for a newer published CLI and writes any notice to
410
+ standard error. It caches the version and check time in `<config directory>/openeng/update-check.json`
411
+ for 24 hours. A stale or missing cache makes one anonymous GET to the npm registry, bounded to 1.5 seconds
412
+ including the response body; a failed check is silent and preserves the command's output, exit code and
413
+ previous cache. Help and `--version` skip the check. Update with `npm install -g @openengio/cli@latest`.
414
+
321
415
  `openeng login` writes one waiting line to standard error while it waits for the browser, so under
322
416
  `--json` standard output stays one JSON value. A loopback listener the system refuses to open, and a
323
417
  session store that cannot be read, written or deleted, each print one line (exit 2) and no stack: the
@@ -334,7 +428,7 @@ for the client that asked for it.
334
428
  ## Output and exit codes
335
429
 
336
430
  Text goes to standard output on success and to standard error on every failure, with one
337
- exception: the violations `openeng types draft validate` answers go to standard output with exit 4. A refusal the host
431
+ exception: the violations `openeng types draft validate` and `openeng types draft try` answer go to standard output with exit 4. A refusal the host
338
432
  answered prints its kind or rule and class, then the refusal catalogue's meaning, then each next
339
433
  option on its own line, naming the command for an option's read where `openeng` has one. When the text
340
434
  is the refusal class's and not the rule's own, the output says so. With `--json` a refusal prints
@@ -386,14 +480,15 @@ built for that host. Otherwise it says the host did not read this `openeng`'s pr
386
480
  the version it read, or `none`. The catalogue explanation also prints. Under `--json`, the refusal
387
481
  carries the host's key as `host` and this client's version as `clientVersion`. The host's `versions`
388
482
  are unchanged: `versions.client` is the version the host read, or null, and `versions.host` is the
389
- host's version.
483
+ host's version. The text also prints `Update with: npm install -g @openengio/cli@latest`, and JSON adds
484
+ `update: "npm install -g @openengio/cli@latest"`.
390
485
 
391
486
  | exit code | meaning |
392
487
  |---|---|
393
488
  | 0 | success |
394
489
  | 2 | the client's own refusal: configuration, host form, store, a missing flag |
395
490
  | 3 | `unauthenticated`: not signed in, a sign-in that was cancelled, timed out or whose state differed, or a logout with nothing stored |
396
- | 4 | a platform refusal (`refused`), a route result whose arm is `refused`, or the violations `openeng types draft validate` answers (printed on standard output) |
491
+ | 4 | a platform refusal (`refused`), a route result whose arm is `refused`, or the violations `openeng types draft validate` or `openeng types draft try` answers (printed on standard output) |
397
492
  | 5 | `unknown-outcome`, from the host or from a transport that lost the answer; also a non-envelope answer with status 500 to 599 |
398
493
  | 6 | `invalid-request`, `not-found` or `bound-exceeded` |
399
494
  | 7 | `internal`, `in-flight`, `protocol-mismatch`, a transport failure with no answer, a non-envelope answer with status 400 to 499, or an internal failure of the client |
@@ -404,7 +499,11 @@ The session of one host is stored under that host's key: `unix:` and the canonic
404
499
  path for a socket host, the origin for an `https` host. A read never falls back to another host's
405
500
  entry. The stores:
406
501
 
407
- - macOS: the keychain, through `security -i` with the command on standard input.
502
+ - macOS: the keychain, through `security -i` with the command on standard input. The value is UTF-8 JSON
503
+ encoded as base64 with an `openeng-session:v1:` marker, so organisation names remain printable
504
+ ASCII in the keychain command. Each write reads back and compares the stored value before it
505
+ succeeds. Reads also accept legacy plain JSON and the hexadecimal output of `security -w`,
506
+ so existing sessions remain usable.
408
507
  - Linux: `secret-tool store`, the secret on standard input.
409
508
  - Otherwise, or with `OPENENG_SESSION_STORE=file`: one file per host in
410
509
  `$XDG_CONFIG_HOME/openeng/sessions/` (else `~/.config/openeng/sessions/`), mode `0600` in a directory of