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 ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Braden Polly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The vendored Unfolded Circle Core API specification in vendor/ is licensed
24
+ separately under CC-BY-SA-4.0; see vendor/README.md.
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # uc-config
2
+
3
+ Configuration-as-code for the [Unfolded Circle Remote 3](https://www.unfoldedcircle.com/).
4
+ Describe activities, button mappings, pages, macros, integrations and settings in
5
+ TypeScript, review a plan against the live remote, then apply it over the local
6
+ Core API. Nothing runs on the remote except its own native configuration.
7
+
8
+ **This project is built to be driven by a coding agent** (Claude Code, Codex,
9
+ Cursor, Hermes, etc.). A person states the intent ("make the play button on the
10
+ Apple TV activity skip chapters") and the agent edits config, validates, plans,
11
+ and applies. The guardrails (three-way drift detection, saved plans, write
12
+ journals, read-only diagnostics) exist so an agent can operate safely on real
13
+ hardware. Humans can use it directly too.
14
+
15
+ > Agents: read [AGENTS.md](AGENTS.md) first, then [docs/snippets.md](docs/snippets.md)
16
+ > for copy-paste recipes. Ask the user only for what you cannot discover:
17
+ > the remote's IP address and the web configurator PIN.
18
+
19
+ ## Quick start
20
+
21
+ Before you start: install Node.js 22+, and on the remote enable the web
22
+ configurator (Settings → Profile → Web configurator). Note the remote's IP and PIN.
23
+
24
+ ```sh
25
+ mkdir my-remote && cd my-remote
26
+ npx uc-config init # package.json, tsconfig, .gitignore, AGENTS.md
27
+ npm install
28
+ ```
29
+
30
+ Then start your coding agent in that folder and prompt:
31
+
32
+ > Set up my Remote 3 at `<IP>`
33
+
34
+ When the agent asks you to authenticate, run `npx uc-config auth` in your own
35
+ terminal and enter the PIN. Don't paste the PIN into chat. That folder is your
36
+ configuration; commit it to a **private** git repo.
37
+
38
+ ## Requirements
39
+
40
+ - Node.js 22+ and npm
41
+ - A Remote 3 on the same LAN, with the **web configurator enabled**
42
+ (Settings → Profile → Web configurator) and its PIN
43
+ - Optional but recommended: the [UC Integration Manager](#integration-manager)
44
+
45
+ Tested against Remote 3 core `0.81.x`, API `0.19.0`. Firmware changes are detected
46
+ and block writes until you reconnect.
47
+
48
+ ## Setup (agent runbook)
49
+
50
+ Run these in the config workspace (the folder created by `init`) using
51
+ `npx uc-config`, or from a clone of this repo using `npm run uc --`, as below.
52
+ Each step is idempotent or fails safely.
53
+
54
+ ```sh
55
+ # 1. Install and build the CLI (dist/ is required by remote.config.ts imports)
56
+ npm install
57
+ npm run build
58
+ npm test # offline, uses a mock Core API
59
+
60
+ # 2. Register the remote under a target name ("home" here). With a single
61
+ # target, later commands pick it automatically; otherwise pass --target.
62
+ npm run uc -- connect home --host http://<REMOTE_IP>
63
+
64
+ # 3. Authenticate. Prompts for the web-configurator PIN in a TTY.
65
+ # Headless agents: have the user run this step, or pass UC_PIN in the env.
66
+ # Never write the PIN or key into files or chat logs.
67
+ npm run uc -- auth
68
+
69
+ # 4. Verify connectivity and API coverage
70
+ npm run uc -- doctor
71
+
72
+ # 5. Snapshot what is on the remote
73
+ npm run uc -- inventory --bindings generated/devices.ts # entity IDs + commands
74
+ npm run uc -- import --out remote.config.ts # editable config
75
+ npm run uc -- diagnose # health check
76
+
77
+ # 6. Take ownership (local only; writes nothing to the remote)
78
+ npm run uc -- compile
79
+ npm run uc -- plan --out .uc/plan.json # expect only "= adopt" operations
80
+ npm run uc -- apply .uc/plan.json --adopt-only
81
+
82
+ # 7. Confirm convergence
83
+ npm run uc -- check # 0 operations, 0 conflicts
84
+ ```
85
+
86
+ After step 7 the workspace is ready. Every later change follows the edit loop below.
87
+
88
+ Notes for agents:
89
+
90
+ - `auth` stores the API key in `.uc/credentials.json` (mode 0600). Alternatively
91
+ set `UC_API_KEY` to an existing key. If a key named `uc-config` already exists
92
+ on the remote, reuse it or revoke it in the configurator first.
93
+ - `inventory`/`import` refuse to overwrite existing files (`wx`). Use new,
94
+ dated filenames when refreshing (e.g. `.uc/devices-2026-10-03.ts`).
95
+ - On macOS, if Node is denied Local Network access the CLI falls back to `curl`
96
+ automatically. If _every_ LAN device is unreachable but the router answers,
97
+ grant Local Network permission to the terminal app that launched the agent.
98
+ - `remote.config.ts`, `generated/` and `.uc/` are gitignored: they describe
99
+ your home and contain credentials. Keep them in a private repo or backup
100
+ (see [Keeping your config private](#keeping-your-config-private)).
101
+
102
+ ## The edit loop
103
+
104
+ ```sh
105
+ # edit remote.config.ts
106
+ npm run check:examples # typecheck config + examples
107
+ npm run uc -- compile # evaluate TS -> .uc/build.json (offline)
108
+ npm run uc -- plan --out .uc/plan.json
109
+ # read every operation, conflict and deferred item; nothing unexpected allowed
110
+ npm run uc -- apply .uc/plan.json
111
+ npm run uc -- check # must report 0 operations
112
+ ```
113
+
114
+ `plan` never writes. `apply` only executes a saved, reviewed plan and re-verifies
115
+ remote identity, firmware and preconditions first. A plan with zero operations
116
+ needs no apply. See [docs/snippets.md](docs/snippets.md) for common edits.
117
+
118
+ ## Diagnostics and fixing problems
119
+
120
+ Run `diagnose` whenever something looks wrong on the remote, after any
121
+ integration update, and before large changes:
122
+
123
+ ```sh
124
+ npm run uc -- diagnose # human-readable; exit 2 if problems found
125
+ npm run uc -- diagnose --json # for agents
126
+ ```
127
+
128
+ It is read-only and reports:
129
+
130
+ - **Orphaned entity references**: activities/macros pointing at an entity that
131
+ no longer exists (the remote shows these as "orphaned" or "unavailable").
132
+ - **Disconnected integrations**: any integration not in `CONNECTED` state.
133
+
134
+ For each orphan it proposes a fix:
135
+
136
+ | Diagnosis | Cause | Fix |
137
+ | ---------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
138
+ | Integration still offers the same entity | Integration update/reinstall dropped configured entities | Re-add it with the printed `api POST ... --write` command, then `plan` (expect 0 ops) |
139
+ | Integration offers a _different_ entity of same type | Driver re-keyed the device (e.g. MAC → serial ID) | Re-add the candidate, replace the old ID **everywhere** in `remote.config.ts`, plan, apply |
140
+ | Integration not `CONNECTED` | Device offline, credentials changed, driver crashed | Reconnect in the configurator or Integration Manager, rerun `diagnose` |
141
+ | No integration owns the ID | Integration removed | Reinstall it (often via the Integration Manager) or remove the references |
142
+
143
+ Plan/apply problems:
144
+
145
+ | Message | Meaning and fix |
146
+ | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
147
+ | `Drift at <fields>` | Someone (or a driver update) changed an owned field on the remote. Copy the live value into source, or rerun `plan --overwrite-drift` only if the user wants the source value restored. |
148
+ | `Managed resource disappeared` | Resource was deleted on the remote. Run `diagnose`; re-add or remove from source. |
149
+ | `Cannot verify command` | `cmd_id` isn't advertised by that entity, or the entity isn't in the activity's `entity_ids`. Check `generated/devices.ts`, refresh inventory. |
150
+ | `Remote firmware changed; reconnect and replan` | Remote auto-updated. Rerun `connect <name> --host ...` (keeps credentials), then plan. |
151
+ | `transport failed` / timeouts | Remote asleep (wake it) or no LAN access from this machine. |
152
+ | Uncertain writes after a crash | Run `resume`. Never blindly rerun apply; use `state adopt KEY ID` if a create actually succeeded. |
153
+
154
+ `npm run uc -- api GET <path>` makes a raw authenticated Core API read (output
155
+ redacted). Non-GET methods require `--write` and should only be used for the
156
+ targeted repairs above.
157
+
158
+ Exit codes: `0` ok, `1` error, `2` drift/conflicts/deferred work/diagnose
159
+ findings, `3` paused waiting for a human step (setup or pairing).
160
+
161
+ ## Integration Manager
162
+
163
+ Some things cannot be done through uc-config alone and need the community
164
+ [**UC Integration Manager**](https://github.com/JackJPowell/uc-intg-manager)
165
+ (runs on the remote at `http://<REMOTE_IP>:9999` while docked, or in Docker):
166
+
167
+ - **Installing custom/community integrations** (Kaleidescape, Oppo, Onkyo/Integra,
168
+ Lutron, Kodi, etc.) from the community registry or a GitHub release. uc-config
169
+ can register an external driver (`driver()`) or upload a local archive
170
+ (`driverArchive()`), but it does not browse, download or version community
171
+ integrations.
172
+ - **Updating integrations**, selecting versions, and rolling back.
173
+ - **Backing up integration configuration** (driver setup data the Core API does
174
+ not export; `import` cannot recover these).
175
+ - **UI diagnostics**: orphaned entities, orphaned IR codesets and unused
176
+ activity entities, plus integration logs.
177
+
178
+ Recommended split: install and update integrations with the Integration Manager,
179
+ then let uc-config own everything that _uses_ them (entities, activities,
180
+ buttons, pages, macros).
181
+
182
+ > **Integration updates can drop configured entities**, even when the manager
183
+ > says configuration is preserved. Every activity using them becomes orphaned.
184
+ > Keep the manager's _Auto update_ setting off, and after every integration
185
+ > update run:
186
+ >
187
+ > ```sh
188
+ > npm run uc -- diagnose && npm run uc -- plan
189
+ > ```
190
+ >
191
+ > Updates marked as _not preserving configuration_ additionally require
192
+ > re-running that integration's setup.
193
+
194
+ ## Keeping your config private
195
+
196
+ This repo holds the tool. Your configuration belongs in its own folder, made
197
+ with `npx uc-config init` (see [Quick start](#quick-start)), in a **private**
198
+ git repo. `init` gitignores `.uc/`, which holds credentials.
199
+
200
+ Alternatively, use `--workspace <dir>` to point the CLI at any directory holding
201
+ `remote.config.ts` and `.uc/`. Back up `.uc/state` and `.uc/journals`
202
+ privately; losing them makes the next plan an adoption pass.
203
+
204
+ ## Reference
205
+
206
+ - [AGENTS.md](AGENTS.md): rules for coding agents working with this repo
207
+ - [docs/snippets.md](docs/snippets.md): common tasks as copy-paste recipes
208
+ - [docs/configuration-authoring.md](docs/configuration-authoring.md): config syntax, ownership, helpers
209
+ - [docs/cli.md](docs/cli.md): every CLI command, provisioning, recovery
210
+ - [examples/full-setup.ts](examples/full-setup.ts): integrations, docks, IR/BT remotes, pairing, profiles
211
+ - [remote.config.example.ts](remote.config.example.ts): minimal starter config
212
+ - [DESIGN.md](DESIGN.md): architecture and design rationale
213
+
214
+ ## Development
215
+
216
+ ```sh
217
+ npm run check # typecheck
218
+ npm test # build + test against a mock Core API
219
+ npm run format # prettier
220
+ npm run generate # regenerate wire types from vendor/core-api.json
221
+ ```
222
+
223
+ Source lives in `src/`. The pinned upstream API specification and its license
224
+ are described in [vendor/README.md](vendor/README.md).
225
+
226
+ ## License
227
+
228
+ MIT, see [LICENSE](LICENSE). The vendored Core API specification is CC-BY-SA-4.0.
229
+ Not affiliated with Unfolded Circle.
@@ -0,0 +1,26 @@
1
+ import { CoreClient } from "./client.js";
2
+ import type { ObjectValue, Resource, State, Observation } from "./model.js";
3
+ export declare const ADAPTER = "ucr3-rest-04b0d08-v1";
4
+ export interface Route {
5
+ collection: string;
6
+ item: string;
7
+ collectionTemplate: string;
8
+ itemTemplate: string;
9
+ idField: string;
10
+ singleton?: boolean;
11
+ }
12
+ export declare function route(r: Resource, id?: string): Route;
13
+ export declare function desired(r: Resource): ObjectValue;
14
+ export declare function importShape(r: Resource, value: ObjectValue): ObjectValue;
15
+ export declare class Adapter {
16
+ readonly client: CoreClient;
17
+ constructor(client: CoreClient);
18
+ private included?;
19
+ private includedEntity;
20
+ observe(original: Resource, state: State, key: string): Promise<Observation>;
21
+ validate(r: Resource, create: boolean): void;
22
+ validateCommands(r: Resource): Promise<void>;
23
+ assertDeletable(r: Resource, id: string): Promise<void>;
24
+ upload(r: Resource, update?: boolean): Promise<string>;
25
+ }
26
+ export declare function validateCommandParameters(command: ObjectValue, metadata: ObjectValue, entity: ObjectValue): void;