uc-config 0.1.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/LICENSE +24 -0
- package/README.md +229 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +540 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +555 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +182 -0
- package/dist/compiler.d.ts +5 -0
- package/dist/compiler.js +155 -0
- package/dist/diagnose.d.ts +41 -0
- package/dist/diagnose.js +133 -0
- package/dist/dsl.d.ts +67 -0
- package/dist/dsl.js +103 -0
- package/dist/engine.d.ts +32 -0
- package/dist/engine.js +276 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.js +124 -0
- package/dist/inventory.d.ts +14 -0
- package/dist/inventory.js +236 -0
- package/dist/model.d.ts +91 -0
- package/dist/model.js +7 -0
- package/dist/planner.d.ts +12 -0
- package/dist/planner.js +248 -0
- package/dist/recovery.d.ts +4 -0
- package/dist/recovery.js +63 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +75 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +101 -0
- package/dist/util.d.ts +18 -0
- package/dist/util.js +140 -0
- package/dist/wire.d.ts +17199 -0
- package/docs/cli.md +125 -0
- package/docs/configuration-authoring.md +196 -0
- package/docs/handoff.md +24 -0
- package/docs/snippets.md +312 -0
- package/examples/full-setup.ts +113 -0
- package/package.json +67 -0
- package/remote.config.example.ts +65 -0
- package/vendor/README.md +9 -0
- package/vendor/core-api.json +1 -0
package/docs/cli.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
Run as `npm run uc -- <command>` inside this repo, or `npx uc-config <command>`
|
|
4
|
+
when installed as a dependency. Global option: `--workspace <dir>` (directory
|
|
5
|
+
holding `remote.config.ts` and `.uc/`; default: current directory).
|
|
6
|
+
|
|
7
|
+
Most commands take `--target <name>`. The default is `$UC_TARGET`; otherwise
|
|
8
|
+
the workspace's only target if exactly one is connected; otherwise `home`.
|
|
9
|
+
|
|
10
|
+
## Commands
|
|
11
|
+
|
|
12
|
+
| Command | Writes to remote | Purpose |
|
|
13
|
+
| ---------------------------------------------- | ----------------- | ----------------------------------------------------------------------- |
|
|
14
|
+
| `init` | no | Scaffold a private config workspace. Never overwrites files. |
|
|
15
|
+
| `connect <name> --host <url>` | no | Record a target (identity, firmware). Rerun after firmware updates. |
|
|
16
|
+
| `auth` | API key only | Exchange the web-configurator PIN (`UC_PIN` or prompt) for an API key. |
|
|
17
|
+
| `doctor` | no | Verify identity and read access to every required endpoint. |
|
|
18
|
+
| `diagnose [--json]` | no | Orphaned entity references, disconnected integrations, suggested fixes. |
|
|
19
|
+
| `inventory [--out f] [--bindings f.ts]` | no | Raw (redacted) remote state; optional typed entity/command bindings. |
|
|
20
|
+
| `import [--out f.ts]` | no | Generate editable config from the live remote. Never overwrites. |
|
|
21
|
+
| `compile [--config f] [--out f]` | no | Evaluate TS config into `.uc/build.json`. Offline. |
|
|
22
|
+
| `plan [--out f] [--prune] [--overwrite-drift]` | no | Three-way diff: source vs last-applied vs live. |
|
|
23
|
+
| `apply <plan> [--adopt-only]` | yes | Execute a saved plan after re-verifying preconditions. |
|
|
24
|
+
| `check` | no | Re-plan and exit 2 if anything differs. |
|
|
25
|
+
| `resume` | maybe | Continue paused setup; reconcile an interrupted apply. |
|
|
26
|
+
| `setup status/respond <key>` | yes | Drive interactive integration/dock setup. |
|
|
27
|
+
| `pairing status/respond <remoteId>` | yes | Bluetooth pairing steps. |
|
|
28
|
+
| `ir learn/capture <emitterId>` | yes | Learn IR codes from a physical remote. |
|
|
29
|
+
| `state adopt <key> <id>` / `forget` / `move` | no | Edit local ownership bindings. |
|
|
30
|
+
| `rollback [--out f]` | no | Build a compensating plan from the last journal. |
|
|
31
|
+
| `backup --out f` | stops intgs | Native full backup. Disruptive; not for routine use. |
|
|
32
|
+
| `api <METHOD> <path> [--data json] [--write]` | only with --write | Raw authenticated Core API call; output redacted. |
|
|
33
|
+
|
|
34
|
+
Exit codes: `0` ok, `1` error, `2` drift/conflicts/deferred/diagnose findings,
|
|
35
|
+
`3` paused for a human step.
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
`auth` prompts for the web configurator PIN. Enable the web configurator on the
|
|
40
|
+
remote first, and approve the key on the remote if asked. Non-interactive
|
|
41
|
+
options: set `UC_PIN`, or set `UC_API_KEY` to an existing key (`connect
|
|
42
|
+
--token-env NAME` changes the variable name). The key is stored in
|
|
43
|
+
`.uc/credentials.json` with mode 0600. Never commit or print it.
|
|
44
|
+
|
|
45
|
+
Configuration secrets use `secret('env:NAME')` or, on macOS,
|
|
46
|
+
`secret('keychain:service/account')`. An optional second argument is a
|
|
47
|
+
non-secret version label that triggers rotation. Raw secret values never appear
|
|
48
|
+
in plans, state or journals.
|
|
49
|
+
|
|
50
|
+
## Adopting an existing remote
|
|
51
|
+
|
|
52
|
+
`import` generates config without taking ownership. The first `plan` shows
|
|
53
|
+
`= adopt` operations; `apply <plan> --adopt-only` records the baseline locally
|
|
54
|
+
and refuses any remote write. Import warnings list what can't be recovered,
|
|
55
|
+
such as original setup credentials, driver archives and binary assets.
|
|
56
|
+
|
|
57
|
+
## Provisioning (new integrations, docks, remotes)
|
|
58
|
+
|
|
59
|
+
A new dependency is created before its dependents. When a plan reports
|
|
60
|
+
**deferred** resources, apply the current phase, then **plan again**. Newly
|
|
61
|
+
created activities and macros first receive their entity membership, which
|
|
62
|
+
exposes the command metadata needed to validate their sequences and buttons.
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm run uc -- setup status media
|
|
66
|
+
npm run uc -- setup respond media --input setup-response.json # {"input_values": {...}}
|
|
67
|
+
npm run uc -- setup respond media --confirm
|
|
68
|
+
npm run uc -- resume
|
|
69
|
+
npm run uc -- plan
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Secret-bearing setup values can be written as `{"$secret":"env:DEVICE_PIN"}`.
|
|
73
|
+
Expired or rejected setup sessions are reported, never silently restarted.
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
npm run uc -- pairing status REMOTE_ENTITY_ID
|
|
77
|
+
npm run uc -- pairing respond REMOTE_ENTITY_ID --input pairing-response.json # {"id":11,"passkey":{"$secret":"env:BT_PASSKEY"}}
|
|
78
|
+
npm run uc -- ir learn EMITTER_ID
|
|
79
|
+
npm run uc -- ir capture EMITTER_ID --out learned.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Installing _community_ integrations (download, version selection, updates) is
|
|
83
|
+
the [UC Integration Manager](https://github.com/JackJPowell/uc-intg-manager)'s
|
|
84
|
+
job. uc-config's `driver()` registers an already-running external driver, and
|
|
85
|
+
`driverArchive()` uploads a local archive.
|
|
86
|
+
|
|
87
|
+
## Ownership, recovery and removal
|
|
88
|
+
|
|
89
|
+
- Plans compare three things: source, the last applied state and live data.
|
|
90
|
+
Live edits to owned fields produce conflicts. Copy the live value into source
|
|
91
|
+
to accept it; `plan --overwrite-drift` restores the source value instead.
|
|
92
|
+
- Apply verifies identity, firmware, state revision, plan integrity and live
|
|
93
|
+
preconditions, and re-reads before each mutation. The Core API is not
|
|
94
|
+
transactional, so avoid configurator edits while an apply runs.
|
|
95
|
+
- Per-target locks prevent two local writers. A lock left by a crashed process
|
|
96
|
+
is kept until someone inspects it.
|
|
97
|
+
- Failed or uncertain writes block the next apply until `resume` runs. A create
|
|
98
|
+
whose outcome is unknown is never replayed; inspect inventory and run
|
|
99
|
+
`state adopt KEY EXACT_ID` if it succeeded.
|
|
100
|
+
- `state move OLD NEW` keeps the remote ID when you rename a logical key.
|
|
101
|
+
`state forget KEY` gives up ownership without deleting anything.
|
|
102
|
+
- Removing a managed resource from source needs `plan --prune` or
|
|
103
|
+
`state forget`. Pruning checks inbound references and never bulk-deletes.
|
|
104
|
+
Integration, driver, dock and entity removal is blocked; handle it explicitly.
|
|
105
|
+
- `rollback` refuses when there is later drift, created or deleted resources,
|
|
106
|
+
fields that were previously absent, or irreversible provisioning.
|
|
107
|
+
Earlier journals stay in `.uc/journals/history/`.
|
|
108
|
+
|
|
109
|
+
## Ownership rules for arrays and fields
|
|
110
|
+
|
|
111
|
+
Arrays (page items, sequences, entity membership) are owned as whole fields.
|
|
112
|
+
Omitting a scalar or object field gives up ownership without clearing it. To
|
|
113
|
+
clear a collection, declare `[]`. Page-item defaults returned by the remote are
|
|
114
|
+
ignored when you didn't declare them.
|
|
115
|
+
|
|
116
|
+
## Limits
|
|
117
|
+
|
|
118
|
+
- Import is not a full disaster-recovery export. Setup secrets, driver binaries
|
|
119
|
+
and some assets cannot be read back.
|
|
120
|
+
- The API doesn't expose installed driver digests, so a driver replaced out of
|
|
121
|
+
band can't be fully detected.
|
|
122
|
+
- Immutable creation fields (e.g. IR remote codeset) need an explicit
|
|
123
|
+
replacement.
|
|
124
|
+
- Firmware installs, Wi-Fi bootstrap and external-service deployment are out of
|
|
125
|
+
scope.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Authoring Remote 3 configuration
|
|
2
|
+
|
|
3
|
+
Edit `remote.config.ts`. It exports a `defineRemote({ schemaVersion: 1, resources })`
|
|
4
|
+
object. TypeScript constants, functions, imports, and object spreads can reuse
|
|
5
|
+
configuration, but the final value must be plain JSON-compatible data. Avoid
|
|
6
|
+
`undefined`, functions, class instances, cycles, non-finite numbers, and side effects.
|
|
7
|
+
Compile executes this trusted code locally; it does not install JavaScript on the remote.
|
|
8
|
+
|
|
9
|
+
## Resource anatomy and identity
|
|
10
|
+
|
|
11
|
+
Each `resources` key is a stable local name. The resource describes native API data:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { defineRemote, ref } from "uc-config";
|
|
15
|
+
|
|
16
|
+
export default defineRemote({
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
resources: {
|
|
19
|
+
"activity.watch_tv": {
|
|
20
|
+
kind: "activity",
|
|
21
|
+
id: "EXISTING_ACTIVITY_ID",
|
|
22
|
+
data: { name: { en_US: "Watch TV" } },
|
|
23
|
+
},
|
|
24
|
+
"activity.watch_tv.button.volume_up_short_press": {
|
|
25
|
+
kind: "activityButton",
|
|
26
|
+
parent: ref("activity.watch_tv"),
|
|
27
|
+
id: "VOLUME_UP/short_press",
|
|
28
|
+
data: {
|
|
29
|
+
short_press: {
|
|
30
|
+
entity_id: "EXACT_CONFIGURED_ENTITY_ID",
|
|
31
|
+
cmd_id: "EXACT_SUPPORTED_COMMAND_ID",
|
|
32
|
+
params: {},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This is a structural example with placeholders, not a deployable setup. The command
|
|
41
|
+
target also needs to be in the activity's `options.entity_ids` before using the binding.
|
|
42
|
+
|
|
43
|
+
| Field | Meaning |
|
|
44
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| Resource map key | Local identity used by references, plans, and state; not a display name. |
|
|
46
|
+
| `kind` | Supported resource kind from `src/model.ts`. |
|
|
47
|
+
| `id` | Exact native remote ID, or kind-specific identity such as `VOLUME_UP/short_press`. Preserve existing IDs. |
|
|
48
|
+
| `parent` | Native parent ID or `ref("logical.key")`. |
|
|
49
|
+
| `data` | Desired writable fields this configuration owns. |
|
|
50
|
+
| `create` | Native creation/setup payload; distinct from update data. Imported resources may lack it. |
|
|
51
|
+
| `dependsOn` | Additional logical resource keys that must be ready first. References also establish dependencies. |
|
|
52
|
+
| `file`, `resourceType` | Local artifact path and asset type for the applicable helpers. Compilation records a content hash. |
|
|
53
|
+
|
|
54
|
+
Use unique keys. JavaScript object construction can overwrite duplicate keys before
|
|
55
|
+
validation. Renaming a key requires updating its references and running
|
|
56
|
+
`npm run uc -- state move OLD NEW`; changing `data.name` does not.
|
|
57
|
+
|
|
58
|
+
## Editing the imported setup
|
|
59
|
+
|
|
60
|
+
Keep the existing native objects unless a reusable helper makes the requested change
|
|
61
|
+
clearer. Rewriting an imported activity with `activity()` can change its locale map
|
|
62
|
+
(`en_US` versus the helper's `en`), add creation inputs, or change owned fields.
|
|
63
|
+
|
|
64
|
+
- **Rename a displayed activity:** edit its existing `data.name` locale entry.
|
|
65
|
+
Preserve its logical key, `id`, and other locales.
|
|
66
|
+
- **Change a physical button:** locate its `activityButton` or `remoteButton`
|
|
67
|
+
resource by parent and button ID. Edit the command under `data.short_press` or
|
|
68
|
+
`data.long_press`; preserve the other bindings. Button IDs use uppercase names
|
|
69
|
+
and a press suffix, for example `HOME/long_press`.
|
|
70
|
+
- **Change a sequence:** edit `data.options.sequences.on` or `.off` for activities,
|
|
71
|
+
or `data.options.sequence` for macros. Native command steps use
|
|
72
|
+
`{ type: "command", command: { entity_id, cmd_id, params } }`; delays use
|
|
73
|
+
`{ type: "delay", delay: 500 }` (milliseconds). Preserve order and other steps.
|
|
74
|
+
- **Change a screen:** edit the appropriate child `activityPage` or `remotePage`.
|
|
75
|
+
Native items use `location: { x, y }` and optional `size: { width, height }`.
|
|
76
|
+
Coordinates are zero-based; items must fit the grid and not overlap.
|
|
77
|
+
- **Use another device:** verify its exact configured entity ID and supported
|
|
78
|
+
command metadata, then include it in the activity/macro's entity membership.
|
|
79
|
+
Display names and available integration entity IDs are not interchangeable with
|
|
80
|
+
configured command target IDs.
|
|
81
|
+
|
|
82
|
+
Arrays are owned as whole fields, including page items, sequences, and entity
|
|
83
|
+
membership. Keep unrelated entries when editing one item. Omitting a field gives
|
|
84
|
+
up ownership without clearing its live value; `[]` explicitly clears a collection.
|
|
85
|
+
Use only native-schema-supported empty values for other fields.
|
|
86
|
+
|
|
87
|
+
Do not reimport over the active source to change one setting. If live values must
|
|
88
|
+
be inspected, import to a new file and selectively reconcile the relevant fields.
|
|
89
|
+
Treat generated inventory/bindings as discovery snapshots that may need refreshing.
|
|
90
|
+
|
|
91
|
+
## Composing new resources with helpers
|
|
92
|
+
|
|
93
|
+
The helper API produces the same resource objects. This example uses placeholders
|
|
94
|
+
that must be replaced with verified inventory values:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import {
|
|
98
|
+
activity,
|
|
99
|
+
bind,
|
|
100
|
+
button,
|
|
101
|
+
command,
|
|
102
|
+
defineRemote,
|
|
103
|
+
delay,
|
|
104
|
+
page,
|
|
105
|
+
ref,
|
|
106
|
+
} from "uc-config";
|
|
107
|
+
|
|
108
|
+
const player = "EXACT_CONFIGURED_ENTITY_ID";
|
|
109
|
+
const home = command(player, "EXACT_SUPPORTED_HOME_COMMAND_ID");
|
|
110
|
+
|
|
111
|
+
export default defineRemote({
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
resources: {
|
|
114
|
+
"activity.watch": activity({
|
|
115
|
+
name: "Watch",
|
|
116
|
+
entities: [player],
|
|
117
|
+
on: [home, delay(500)],
|
|
118
|
+
}),
|
|
119
|
+
"activity.watch.home": bind(ref("activity.watch"), "HOME", home),
|
|
120
|
+
"activity.watch.controls": page(ref("activity.watch"), {
|
|
121
|
+
name: "Controls",
|
|
122
|
+
grid: { width: 4, height: 6 },
|
|
123
|
+
items: [button({ label: "Home", at: [0, 0], command: home })],
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Helpers `activity({ on, off })` and `macro({ steps })` wrap command steps for you;
|
|
130
|
+
do not pass already-wrapped native command steps to these arrays. `button()` takes
|
|
131
|
+
`at: [x, y]` and optional `size: [width, height]`, unlike native item objects.
|
|
132
|
+
|
|
133
|
+
`bind(parent, button, command, press, scope)` defaults to `short_press` and
|
|
134
|
+
`activity`. `page(parent, data, id, scope)` also defaults to activity scope.
|
|
135
|
+
For remote children choose `"remote"`; for commands local to that remote use
|
|
136
|
+
`localCommand(cmdId, params?)`, which omits `entity_id`. Verify supported commands
|
|
137
|
+
instead of inferring their names.
|
|
138
|
+
|
|
139
|
+
For native structures without a dedicated helper, use `resource(kind, options)`.
|
|
140
|
+
Exported `Schemas` exposes native request types. See [the complete template](../examples/full-setup.ts)
|
|
141
|
+
and [the helper implementation](../src/dsl.ts) for exact signatures.
|
|
142
|
+
|
|
143
|
+
## Integrations and provisioning
|
|
144
|
+
|
|
145
|
+
Use the driver's advertised setup schema; there is no universal device setup payload.
|
|
146
|
+
`integration()` takes a driver ID/reference, name, and driver-specific `setup` data.
|
|
147
|
+
`entity()` selects one exact available entity using its integration parent and
|
|
148
|
+
`entityId`. Do not configure every discovered entity implicitly.
|
|
149
|
+
|
|
150
|
+
`driver()` registers an already-running external service; it does not deploy that
|
|
151
|
+
service. `driverArchive()` installs a local archive. `dock()`, `remote()`,
|
|
152
|
+
`pairing()`, and `irCode()` cover their respective native setup resources. See the
|
|
153
|
+
README and complete template before adding these resources.
|
|
154
|
+
|
|
155
|
+
Use `secret("env:VARIABLE")` or `secret("keychain:service/account")` for secret
|
|
156
|
+
inputs. An optional second argument is a non-secret rotation version. Never put
|
|
157
|
+
PINs/API keys into source or print `.uc/credentials.json`.
|
|
158
|
+
|
|
159
|
+
New resources may require multiple plan/apply phases. Integration setup and pairing
|
|
160
|
+
can pause for user interaction. Inspect `setup status`, provide the requested input
|
|
161
|
+
through `setup respond`, and use `resume` as documented in [the CLI reference](cli.md). Replan after
|
|
162
|
+
each completed phase; never bypass deferred dependency or command validation.
|
|
163
|
+
|
|
164
|
+
Imported configuration is not a full disaster-recovery backup: original setup
|
|
165
|
+
credentials, driver archives, and binary assets may be missing. Installing and
|
|
166
|
+
updating community integrations is handled by the
|
|
167
|
+
[UC Integration Manager](https://github.com/JackJPowell/uc-intg-manager); run
|
|
168
|
+
`npm run uc -- diagnose` after any integration update.
|
|
169
|
+
|
|
170
|
+
## Review and apply
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
npm run check:examples
|
|
174
|
+
npm run uc -- compile
|
|
175
|
+
npm run uc -- plan --out .uc/plan.json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Typechecking checks source shape. Compile evaluates source and validates local
|
|
179
|
+
structure into `.uc/build.json`. Plan reads the remote and compares desired values,
|
|
180
|
+
live values, and the local baseline; it does not deploy changes. Inspect all
|
|
181
|
+
operations, deferred work, and conflicts. Do not automatically resolve drift by
|
|
182
|
+
adding `--overwrite-drift`. Removing resources requires an intentional prune or
|
|
183
|
+
`state forget`; see [the CLI reference](cli.md) for supported removal behavior.
|
|
184
|
+
|
|
185
|
+
When deployment is authorized, apply the reviewed plan, then verify:
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
npm run uc -- apply .uc/plan.json
|
|
189
|
+
npm run uc -- check
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
After any source change, compile and plan again. Never hand-edit a plan or local
|
|
193
|
+
state to bypass validation. A plan with no changes needs no apply. An adoption-only
|
|
194
|
+
plan can use `apply .uc/plan.json --adopt-only` to record ownership locally while
|
|
195
|
+
refusing remote writes. `npm run build` builds the CLI itself; it is separate from
|
|
196
|
+
configuration compilation.
|
package/docs/handoff.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Local state that git does not hold
|
|
2
|
+
|
|
3
|
+
Your remote's configuration and operating state are deliberately kept out of
|
|
4
|
+
this repository. A fresh clone contains the tool only.
|
|
5
|
+
|
|
6
|
+
| Path | Contents | Back up? |
|
|
7
|
+
| ---------------------- | ------------------------------------------------------------ | ------------------------- |
|
|
8
|
+
| `remote.config.ts` | Your desired configuration | Yes (private git repo) |
|
|
9
|
+
| `generated/devices.ts` | Entity/command bindings from `inventory --bindings` | Optional (regenerable) |
|
|
10
|
+
| `.uc/targets/` | Remote address, identity and firmware | Yes |
|
|
11
|
+
| `.uc/credentials.json` | API key (mode 0600) | No; re-run `auth` if lost |
|
|
12
|
+
| `.uc/state/` | Logical key → remote ID bindings and the last applied values | **Yes, critical** |
|
|
13
|
+
| `.uc/journals/` | Apply journals used by `resume` and `rollback` | Yes |
|
|
14
|
+
| `.uc/*.json` | Builds, plans, inventories | No |
|
|
15
|
+
|
|
16
|
+
If you lose `.uc/state`, the next plan becomes an adoption pass. Recreate it
|
|
17
|
+
with `import`, review the result, and then `apply --adopt-only`. Never apply a
|
|
18
|
+
plan that unexpectedly creates resources which already exist on the remote.
|
|
19
|
+
|
|
20
|
+
When you move to a new machine, copy `.uc/` across (e.g. `rsync -a old/.uc/ new/.uc/`)
|
|
21
|
+
rather than re-running `connect`/`auth`/`import`. A new session or new clone
|
|
22
|
+
should reuse existing state, not start over.
|
|
23
|
+
|
|
24
|
+
For the full first-run procedure see the README's "Setup (agent runbook)".
|
package/docs/snippets.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Snippets: common tasks
|
|
2
|
+
|
|
3
|
+
Copy-paste recipes for the things people most often ask for. Each one ends with
|
|
4
|
+
the same validation loop:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm run uc -- compile && npm run uc -- plan --out .uc/plan.json
|
|
8
|
+
# review: only the intended resources may change
|
|
9
|
+
npm run uc -- apply .uc/plan.json && npm run uc -- check
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Ground rules (see [AGENTS.md](../AGENTS.md)):
|
|
13
|
+
|
|
14
|
+
- Get every entity ID and `cmd_id` from `generated/devices.ts` or a fresh
|
|
15
|
+
inventory. Never guess command names.
|
|
16
|
+
- A command used in an activity must belong to an entity in that activity's
|
|
17
|
+
`entity_ids`.
|
|
18
|
+
- Arrays (`entity_ids`, sequences, page `items`) are replaced whole. Keep the
|
|
19
|
+
existing entries and their order.
|
|
20
|
+
- Imported configs use native objects; new resources can use the helpers
|
|
21
|
+
(`activity()`, `bind()`, `page()`, ...). Both forms are valid together.
|
|
22
|
+
|
|
23
|
+
## Finding things
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# Which entities exist and what commands do they accept?
|
|
27
|
+
grep -n 'name:' generated/devices.ts
|
|
28
|
+
grep -n 'Apple TV' generated/devices.ts
|
|
29
|
+
|
|
30
|
+
# Refresh bindings after adding devices/integrations (files are never overwritten)
|
|
31
|
+
npm run uc -- inventory --bindings .uc/devices-$(date +%F).ts
|
|
32
|
+
|
|
33
|
+
# Where is a resource defined?
|
|
34
|
+
grep -n '"activity\.' remote.config.ts | grep -v '\.button\.\|\.page\.' # activities
|
|
35
|
+
grep -n 'activity.watch_tv.button' remote.config.ts # its buttons
|
|
36
|
+
grep -n '<entity-id>' remote.config.ts # every use of an entity
|
|
37
|
+
|
|
38
|
+
# Raw API reads (redacted)
|
|
39
|
+
npm run uc -- api GET /activities
|
|
40
|
+
npm run uc -- api GET '/intg/instances/<integration_id>/entities?filter=ALL'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Physical button names: `BACK HOME VOICE VOLUME_UP VOLUME_DOWN MUTE DPAD_UP
|
|
44
|
+
DPAD_DOWN DPAD_LEFT DPAD_RIGHT DPAD_MIDDLE CHANNEL_UP CHANNEL_DOWN PREV PLAY
|
|
45
|
+
NEXT POWER RECORD MENU STOP`. Check `npm run uc -- api GET /cfg/device/button_layout` for the exact list.
|
|
46
|
+
|
|
47
|
+
## Remap a physical button in an activity
|
|
48
|
+
|
|
49
|
+
Imported form: change only `cmd_id` (and `entity_id` if needed).
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
"activity.watch_kaleidescape.button.next_short_press": {
|
|
53
|
+
kind: "activityButton",
|
|
54
|
+
parent: { $ref: "activity.watch_kaleidescape" },
|
|
55
|
+
id: "NEXT/short_press",
|
|
56
|
+
data: {
|
|
57
|
+
short_press: {
|
|
58
|
+
entity_id: "kaleidescape.main.media_player.XXXX",
|
|
59
|
+
cmd_id: "media_player.next", // was media_player.fast_forward
|
|
60
|
+
params: {},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
New binding with the helper:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { bind, command, ref } from "uc-config";
|
|
70
|
+
|
|
71
|
+
"activity.watch_tv.button.menu": bind(
|
|
72
|
+
ref("activity.watch_tv"),
|
|
73
|
+
"MENU",
|
|
74
|
+
command(appleTv, "media_player.context_menu"),
|
|
75
|
+
),
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Add a long-press action
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
"activity.watch_tv.button.home_long": bind(
|
|
82
|
+
ref("activity.watch_tv"),
|
|
83
|
+
"HOME",
|
|
84
|
+
command(appleTv, "APP_SWITCHER"),
|
|
85
|
+
"long_press",
|
|
86
|
+
),
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Route volume to the receiver in every activity
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const receiver = "onkyo_driver.main.media_player.XXXX";
|
|
93
|
+
const volumeButtons = (activityKey: string) => ({
|
|
94
|
+
[`${activityKey}.button.vol_up`]: bind(ref(activityKey), "VOLUME_UP", command(receiver, "media_player.volume_up")),
|
|
95
|
+
[`${activityKey}.button.vol_down`]: bind(ref(activityKey), "VOLUME_DOWN", command(receiver, "media_player.volume_down")),
|
|
96
|
+
[`${activityKey}.button.mute`]: bind(ref(activityKey), "MUTE", command(receiver, "media_player.mute_toggle")),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
resources: {
|
|
100
|
+
...volumeButtons("activity.watch_tv"),
|
|
101
|
+
...volumeButtons("activity.play_ps5"),
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If an activity already has imported `VOLUME_UP/short_press` resources, edit
|
|
106
|
+
those instead. Two resources with the same parent and `id` conflict.
|
|
107
|
+
|
|
108
|
+
## Add an entity to an activity
|
|
109
|
+
|
|
110
|
+
Append to `entity_ids` and keep the existing order. Required before any of its
|
|
111
|
+
commands can be used in that activity.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
options: {
|
|
115
|
+
entity_ids: [
|
|
116
|
+
"lgwebos_driver.main.media_player.XXXX",
|
|
117
|
+
"onkyo_driver.main.media_player.XXXX",
|
|
118
|
+
"kaleidescape.main.remote.XXXX", // added
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Change an activity's power-on / power-off sequence
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
sequences: {
|
|
127
|
+
on: [
|
|
128
|
+
{ type: "command", command: { entity_id: tv, cmd_id: "media_player.on", params: {} } },
|
|
129
|
+
{ type: "delay", delay: 2000 },
|
|
130
|
+
{ type: "command", command: { entity_id: receiver, cmd_id: "media_player.select_source", params: { source: "BD/DVD" } } },
|
|
131
|
+
],
|
|
132
|
+
off: [
|
|
133
|
+
{ type: "command", command: { entity_id: receiver, cmd_id: "media_player.off", params: {} } },
|
|
134
|
+
{ type: "command", command: { entity_id: tv, cmd_id: "media_player.off", params: {} } },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`select_source` values are entity-specific. Copy them from an existing sequence
|
|
140
|
+
or the entity's `attributes.source_list` (`npm run uc -- api GET /entities/<id>`).
|
|
141
|
+
|
|
142
|
+
## Create a new activity
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { activity, command, delay, ref, bind, page, button } from "uc-config";
|
|
146
|
+
|
|
147
|
+
"activity.movie_night": activity({
|
|
148
|
+
name: "Movie Night",
|
|
149
|
+
entities: [tv, receiver, player, lights],
|
|
150
|
+
on: [
|
|
151
|
+
command(lights, "light.off"),
|
|
152
|
+
command(tv, "media_player.on"),
|
|
153
|
+
delay(1500),
|
|
154
|
+
command(receiver, "media_player.on"),
|
|
155
|
+
],
|
|
156
|
+
off: [command(receiver, "media_player.off"), command(tv, "media_player.off")],
|
|
157
|
+
}),
|
|
158
|
+
"activity.movie_night.button.play": bind(ref("activity.movie_night"), "PLAY", command(player, "media_player.play_pause")),
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
New activities apply in two phases: the first apply creates the activity and
|
|
162
|
+
its entity membership, then **plan again** to apply buttons/pages (`deferred`
|
|
163
|
+
in the plan output tells you this).
|
|
164
|
+
|
|
165
|
+
## Add a touchscreen page with buttons
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
"activity.watch_tv.page.apps": page(ref("activity.watch_tv"), {
|
|
169
|
+
name: "Apps",
|
|
170
|
+
grid: { width: 4, height: 6 },
|
|
171
|
+
items: [
|
|
172
|
+
button({ label: "Netflix", at: [0, 0], size: [2, 1], command: command(tv, "NETFLIX") }),
|
|
173
|
+
button({ label: "Prime", at: [2, 0], size: [2, 1], command: command(tv, "AMAZON") }),
|
|
174
|
+
button({ label: "Fix Inputs", at: [1, 2], size: [2, 2], command: command(fixMacro, "macro.run") }),
|
|
175
|
+
],
|
|
176
|
+
}),
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Items must fit within the grid and must not overlap. For an existing imported
|
|
180
|
+
page, edit its `items` array in place.
|
|
181
|
+
|
|
182
|
+
## Create a macro ("fix inputs" style)
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { macro, command, delay } from "uc-config";
|
|
186
|
+
|
|
187
|
+
"macro.fix_inputs": macro({
|
|
188
|
+
name: "Fix Inputs",
|
|
189
|
+
entities: [tv, receiver],
|
|
190
|
+
steps: [
|
|
191
|
+
command(tv, "media_player.select_source", { source: "HDMI 2" }),
|
|
192
|
+
delay(500),
|
|
193
|
+
command(receiver, "media_player.select_source", { source: "BD/DVD" }),
|
|
194
|
+
],
|
|
195
|
+
}),
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Run it from a button or page with `command("<macro entity id>", "macro.run")`.
|
|
199
|
+
For an existing macro the entity ID is its `id` in `remote.config.ts`. For a
|
|
200
|
+
new one, apply first, then read it from `npm run uc -- api GET /macros`.
|
|
201
|
+
|
|
202
|
+
## Touch slider target
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
options: {
|
|
206
|
+
touch_slider: { enabled: true, target: { entity_id: "lutron_driver.main.light.XXXX" } },
|
|
207
|
+
},
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Rename an activity / page
|
|
211
|
+
|
|
212
|
+
Change the display name only; keep the resource key and `id`:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
data: { name: { en_US: "Watch Movies" } }, // was "Watch Kaleidescape"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
To rename the _logical key_ too, edit the key and every `ref()`/`$ref` to it,
|
|
219
|
+
then run `npm run uc -- state move OLD_KEY NEW_KEY`.
|
|
220
|
+
|
|
221
|
+
## Device settings
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { settings } from "uc-config";
|
|
225
|
+
|
|
226
|
+
"settings.display": settings("display", { brightness: 60, auto_brightness: true }),
|
|
227
|
+
"settings.haptic": settings("haptic", { enabled: true }),
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Only declared fields are owned. Sections: `device display button haptic
|
|
231
|
+
localization power_saving sound bt profile voice_control`.
|
|
232
|
+
|
|
233
|
+
## Repair after an integration update (orphaned entities)
|
|
234
|
+
|
|
235
|
+
```sh
|
|
236
|
+
npm run uc -- diagnose
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Entity dropped but still offered** (diagnose prints `fix: Re-add ...`):
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
npm run uc -- api POST '/intg/instances/<integration_id>/entities/<local_id>' --data '{}' --write
|
|
243
|
+
npm run uc -- diagnose # expect All clear
|
|
244
|
+
npm run uc -- plan # expect 0 operations
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Entity re-keyed** (diagnose lists a `candidate`):
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
npm run uc -- api POST '/intg/instances/<integration_id>/entities/<candidate_local_id>' --data '{}' --write
|
|
251
|
+
grep -n '<old-entity-id>' remote.config.ts # entity_ids, sequences, buttons, pages, macros
|
|
252
|
+
# replace every occurrence with the new ID, then:
|
|
253
|
+
npm run uc -- compile && npm run uc -- plan --out .uc/plan.json && npm run uc -- apply .uc/plan.json
|
|
254
|
+
npm run uc -- diagnose
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
If the plan reports `Cannot verify command` for the swapped entity, the
|
|
258
|
+
activity's live `included_entities` still lists the old one. Apply the
|
|
259
|
+
`entity_ids` change first, then replan for the buttons and sequences.
|
|
260
|
+
|
|
261
|
+
**Drift from a driver renaming something** (`Drift at data.name`): copy the
|
|
262
|
+
live value (shown in the plan) into source, then:
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
npm run uc -- state forget <key> && npm run uc -- state adopt <key> <native-id>
|
|
266
|
+
npm run uc -- compile && npm run uc -- plan --out .uc/plan.json
|
|
267
|
+
npm run uc -- apply .uc/plan.json --adopt-only
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Pull changes made in the web configurator
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
npm run uc -- import --out .uc/imported-$(date +%F).config.ts
|
|
274
|
+
npm run uc -- compile --config .uc/imported-$(date +%F).config.ts --out .uc/imported-build.json
|
|
275
|
+
diff <(jq -S . .uc/build.json) <(jq -S . .uc/imported-build.json) | less
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Imported keys are generic (`activity3`). Match resources by `kind` + `id` and
|
|
279
|
+
copy the changed fields into `remote.config.ts`. Then `plan` should report
|
|
280
|
+
0 operations.
|
|
281
|
+
|
|
282
|
+
## Undo the last apply
|
|
283
|
+
|
|
284
|
+
```sh
|
|
285
|
+
npm run uc -- rollback --out .uc/rollback.json # builds a compensating plan
|
|
286
|
+
# review it like any plan, then:
|
|
287
|
+
npm run uc -- apply .uc/rollback.json
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Back up everything (scheduled job friendly)
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
D=backups/$(date +%F); mkdir -p "$D"
|
|
294
|
+
npm run -s uc -- inventory --out "$D/inventory.json" # redacted raw state
|
|
295
|
+
npm run -s uc -- import --out "$D/remote.config.ts" # readable snapshot
|
|
296
|
+
tar czf "$D/dotuc.tgz" --exclude credentials.json .uc # ownership state
|
|
297
|
+
curl -sf http://<REMOTE_IP>:9999/api/backups/download -o "$D/intg-manager.json" # Integration Manager
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`npm run uc -- backup --out PRIVATE_PATH` produces the remote's native full
|
|
301
|
+
backup, but it **temporarily stops integrations and docks**, so don't schedule it.
|
|
302
|
+
|
|
303
|
+
## Nightly health check (for an agent cron job)
|
|
304
|
+
|
|
305
|
+
```sh
|
|
306
|
+
npm run -s uc -- diagnose --json > .uc/diagnose.json; d=$?
|
|
307
|
+
npm run -s uc -- plan --out .uc/plan-nightly.json; p=$?
|
|
308
|
+
echo "diagnose=$d plan=$p" # 0/0 = healthy; 2 = needs attention
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Report findings, and fix only the clear-cut cases (re-add a dropped entity that
|
|
312
|
+
is still offered). Escalate everything else to the user.
|