uc-config 0.1.0 → 0.2.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ Each release says whether upgrading needs any **action**. To upgrade a config
4
+ workspace:
5
+
6
+ ```sh
7
+ npm install uc-config@latest
8
+ npx uc-config init --refresh-docs
9
+ npx uc-config compile && npx uc-config plan # must show 0 operations
10
+ ```
11
+
12
+ ## 0.2.1
13
+
14
+ Action required: run `npx uc-config init --refresh-docs`. It updates the
15
+ scaffolded AGENTS.md, whose upgrade instructions were wrong.
16
+
17
+ - `init --refresh-docs` updates AGENTS.md and CLAUDE.md to the installed
18
+ version. Files you edited are kept, and the new version is written beside
19
+ them as `.new`.
20
+ - `doctor` prints a notice when a newer version is published. Set
21
+ `UC_NO_UPDATE_CHECK=1` to disable it.
22
+ - The scaffolded upgrade instructions use `npm install uc-config@latest`.
23
+ `npm update` doesn't move between 0.x minor versions.
24
+
25
+ ## 0.2.0
26
+
27
+ Action required: none.
28
+
29
+ - `init` asks for the remote's IP and the web configurator PIN, then connects
30
+ and authenticates. It can be rerun safely.
31
+ - `init` writes CLAUDE.md, which points Claude Code at AGENTS.md.
32
+
33
+ ## 0.1.0
34
+
35
+ First release.
package/README.md CHANGED
@@ -16,24 +16,88 @@ hardware. Humans can use it directly too.
16
16
  > for copy-paste recipes. Ask the user only for what you cannot discover:
17
17
  > the remote's IP address and the web configurator PIN.
18
18
 
19
- ## Quick start
19
+ ## Quick start (for humans)
20
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.
21
+ ### 1. Before you start
22
+
23
+ - Install [Node.js](https://nodejs.org/) 22 or newer.
24
+ - On the remote, enable the web configurator: **Settings → Profile → Web
25
+ configurator**. Note the **PIN** it shows.
26
+ - Find the remote's **IP address**. It's shown in the remote's network
27
+ settings, or in your router's device list. The computer you use must be on the same network as the remote.
28
+
29
+ ### 2. Create your config folder
23
30
 
24
31
  ```sh
25
32
  mkdir my-remote && cd my-remote
26
- npx uc-config init # package.json, tsconfig, .gitignore, AGENTS.md
33
+ npx uc-config init
27
34
  npm install
28
35
  ```
29
36
 
30
- Then start your coding agent in that folder and prompt:
37
+ `init` creates the project files, then asks for the remote's IP address and
38
+ the web configurator PIN. The PIN is typed hidden in your terminal and is only
39
+ used once, to create an API key saved in `.uc/credentials.json`. Press Enter at
40
+ either prompt to skip it; you can rerun `npx uc-config init` at any time and it
41
+ picks up where it left off without overwriting anything.
42
+
43
+ If `init` says a key named `uc-config` already exists, revoke that key in the
44
+ web configurator and run `npx uc-config init` again.
45
+
46
+ This folder holds your remote's configuration. It's yours, not part of this
47
+ repo.
48
+
49
+ ### 3. Let your coding agent set it up
50
+
51
+ Start your coding agent (Claude Code, Codex, Cursor, etc.) in that folder and
52
+ prompt:
31
53
 
32
54
  > Set up my Remote 3 at `<IP>`
33
55
 
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.
56
+ The agent imports your current setup into `remote.config.ts` and records which
57
+ resources it manages. This writes nothing to the remote.
58
+
59
+ If you skipped the PIN during `init`, the agent will ask you to run
60
+ `npx uc-config auth` in your own terminal. **Don't paste the PIN into the
61
+ chat.**
62
+
63
+ `init` also writes `CLAUDE.md`, which points Claude Code at `AGENTS.md`;
64
+ Claude Code reads `CLAUDE.md` rather than `AGENTS.md`.
65
+
66
+ ### 4. Save your config
67
+
68
+ Commit the folder to a **private** git repo. It contains your device IDs and
69
+ IP addresses. `.uc/` (credentials and state) is already gitignored; back it up
70
+ separately.
71
+
72
+ ### Day to day
73
+
74
+ Ask your agent for changes in plain language, for example:
75
+
76
+ > Make the NEXT button skip chapters in the movie activity
77
+
78
+ > Add a page to Watch TV with buttons for Netflix and YouTube
79
+
80
+ > Something on the remote says "orphaned entity". Fix it.
81
+
82
+ The agent shows a plan of what will change before applying it. After you update
83
+ an integration in the Integration Manager, ask the agent to run diagnostics.
84
+
85
+ ## Updating
86
+
87
+ Ask your agent to "update uc-config", or run in your config folder:
88
+
89
+ ```sh
90
+ npm install uc-config@latest
91
+ npx uc-config init --refresh-docs # refresh AGENTS.md / CLAUDE.md
92
+ npx uc-config compile && npx uc-config plan # must show 0 operations
93
+ ```
94
+
95
+ Use `npm install uc-config@latest` rather than `npm update`: before 1.0,
96
+ `npm update` doesn't move between minor versions (0.2 → 0.3). `--refresh-docs`
97
+ keeps files you've edited and writes the new version beside them as `.new`.
98
+ `doctor` tells you when a newer version is available. See
99
+ [CHANGELOG.md](CHANGELOG.md) for what changed and whether a release needs any
100
+ action.
37
101
 
38
102
  ## Requirements
39
103
 
@@ -57,6 +121,9 @@ npm install
57
121
  npm run build
58
122
  npm test # offline, uses a mock Core API
59
123
 
124
+ # Steps 2-3 are usually already done by `npx uc-config init`. Skip them if
125
+ # .uc/targets/*.json and .uc/credentials.json exist.
126
+
60
127
  # 2. Register the remote under a target name ("home" here). With a single
61
128
  # target, later commands pick it automatically; otherwise pass --target.
62
129
  npm run uc -- connect home --host http://<REMOTE_IP>
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { writeFile, mkdir } from "node:fs/promises";
4
4
  import { resolve, join } from "node:path";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { Writable } from "node:stream";
7
- import { CoreClient, resolveSecrets } from "./client.js";
7
+ import { ApiError, CoreClient, resolveSecrets } from "./client.js";
8
8
  import { Adapter } from "./adapter.js";
9
9
  import { compile, validateConfig } from "./compiler.js";
10
10
  import { Engine, PendingSetup } from "./engine.js";
@@ -123,7 +123,18 @@ program
123
123
  .requiredOption("--host <url>")
124
124
  .option("--token-env <name>", "API key environment variable", "UC_API_KEY")
125
125
  .action(async (name, o) => {
126
- const client = new CoreClient(o.host);
126
+ await connectTarget(name, o.host, o.tokenEnv);
127
+ });
128
+ program
129
+ .command("auth")
130
+ .option("--target <name>", "target", defaultTarget)
131
+ .action(async (o) => {
132
+ await load(o.target); // fail on a missing target before prompting
133
+ const pin = process.env.UC_PIN ?? (await hiddenPrompt("Web configurator PIN: "));
134
+ await authenticate(o.target, pin);
135
+ });
136
+ async function connectTarget(name, host, tokenEnv = "UC_API_KEY") {
137
+ const client = new CoreClient(host);
127
138
  const version = await client.version();
128
139
  if (version.model !== "UCR3" || typeof version.address !== "string")
129
140
  throw new Error("Expected an identifiable Remote 3");
@@ -131,7 +142,7 @@ program
131
142
  host: client.base.origin,
132
143
  identity: version.address,
133
144
  version,
134
- tokenEnv: o.tokenEnv,
145
+ tokenEnv,
135
146
  };
136
147
  try {
137
148
  const old = await readJson(targetFile(name));
@@ -144,35 +155,79 @@ program
144
155
  }
145
156
  await saveJson(targetFile(name), target);
146
157
  console.log(`Connected ${name}: ${version.model}, core ${version.core}, API ${version.api}. No configuration changed.`);
147
- });
148
- program
149
- .command("auth")
150
- .option("--target <name>", "target", defaultTarget)
151
- .action(async (o) => {
152
- const { target } = await load(o.target);
153
- const pin = process.env.UC_PIN ?? (await hiddenPrompt("Web configurator PIN: "));
154
- const client = new CoreClient(target.host);
155
- validateRequest("/auth/api_keys", "POST", {
156
- name: "uc-config",
157
- scopes: ["admin"],
158
- });
159
- const { data } = await client.request("POST", "/auth/api_keys", { name: "uc-config", scopes: ["admin"] }, {
160
- Authorization: `Basic ${Buffer.from(`web-configurator:${pin}`).toString("base64")}`,
161
- });
162
- if (typeof data.api_key !== "string")
163
- throw new Error("No API key returned");
164
- let credentials = {};
158
+ return target;
159
+ }
160
+ async function hasCredentials(target) {
161
+ if (process.env[target.tokenEnv])
162
+ return true;
165
163
  try {
166
- credentials = await readJson(local("credentials.json"));
164
+ const c = await readJson(local("credentials.json"));
165
+ return typeof c[target.identity] === "string";
167
166
  }
168
- catch (e) {
169
- if (e.code !== "ENOENT")
170
- throw e;
167
+ catch {
168
+ return false;
171
169
  }
172
- credentials[target.identity] = data.api_key;
173
- await saveJson(local("credentials.json"), credentials);
174
- console.log("API key saved in .uc/credentials.json (mode 0600, gitignored). Approve it on the remote if requested, then run doctor.");
175
- });
170
+ }
171
+ async function authenticate(name, pin) {
172
+ {
173
+ const { target } = await load(name);
174
+ const client = new CoreClient(target.host);
175
+ validateRequest("/auth/api_keys", "POST", {
176
+ name: "uc-config",
177
+ scopes: ["admin"],
178
+ });
179
+ const { data } = await client.request("POST", "/auth/api_keys", { name: "uc-config", scopes: ["admin"] }, {
180
+ Authorization: `Basic ${Buffer.from(`web-configurator:${pin}`).toString("base64")}`,
181
+ });
182
+ if (typeof data.api_key !== "string")
183
+ throw new Error("No API key returned");
184
+ let credentials = {};
185
+ try {
186
+ credentials = await readJson(local("credentials.json"));
187
+ }
188
+ catch (e) {
189
+ if (e.code !== "ENOENT")
190
+ throw e;
191
+ }
192
+ credentials[target.identity] = data.api_key;
193
+ await saveJson(local("credentials.json"), credentials);
194
+ console.log("API key saved in .uc/credentials.json (mode 0600, gitignored). Approve it on the remote if requested, then run doctor.");
195
+ }
196
+ }
197
+ /** Latest published version, or undefined if offline/disabled. Never throws. */
198
+ async function latestVersion() {
199
+ if (process.env.UC_NO_UPDATE_CHECK)
200
+ return undefined;
201
+ try {
202
+ const res = await fetch("https://registry.npmjs.org/uc-config/latest", {
203
+ signal: AbortSignal.timeout(3000),
204
+ });
205
+ if (!res.ok)
206
+ return undefined;
207
+ const v = (await res.json()).version;
208
+ return typeof v === "string" ? v : undefined;
209
+ }
210
+ catch {
211
+ return undefined;
212
+ }
213
+ }
214
+ function newer(a, b) {
215
+ const p = (v) => v.split("-")[0].split(".").map(Number);
216
+ const [x, y] = [p(a), p(b)];
217
+ for (let i = 0; i < 3; i++)
218
+ if ((x[i] ?? 0) !== (y[i] ?? 0))
219
+ return (x[i] ?? 0) > (y[i] ?? 0);
220
+ return false;
221
+ }
222
+ async function question(label) {
223
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
224
+ try {
225
+ return (await rl.question(label)).trim();
226
+ }
227
+ finally {
228
+ rl.close();
229
+ }
230
+ }
176
231
  program
177
232
  .command("doctor")
178
233
  .option("--target <name>", "target", defaultTarget)
@@ -180,6 +235,9 @@ program
180
235
  const { client, target } = await load(o.target);
181
236
  await client.verifyTarget(target);
182
237
  console.log(`Identity verified; core ${target.version.core}, reported API ${target.version.api}`);
238
+ const latest = await latestVersion();
239
+ if (latest && newer(latest, pkgVersion))
240
+ console.log(`Update available: uc-config ${pkgVersion} -> ${latest}. Run: npm install uc-config@latest && npx uc-config init --refresh-docs (see CHANGELOG.md)`);
183
241
  for (const path of [
184
242
  "/entities",
185
243
  "/activities",
@@ -506,15 +564,90 @@ ir.command("capture <emitterId>")
506
564
  program
507
565
  .command("init")
508
566
  .description("Scaffold a private config workspace (package.json, tsconfig, .gitignore, AGENTS.md)")
509
- .action(async () => {
510
- const r = await init(root(), pkgVersion);
567
+ .option("--host <ip>", "remote IP address (skips the prompt)")
568
+ .option("--target <name>", "target name", "home")
569
+ .option("--no-connect", "only create files; don't connect or authenticate")
570
+ .option("--refresh-docs", "update AGENTS.md/CLAUDE.md to this version (edited files get a .new copy)")
571
+ .action(async (o) => {
572
+ const r = await init(root(), pkgVersion, { refreshDocs: o.refreshDocs });
511
573
  for (const f of r.written)
512
574
  console.log(`created ${f}`);
513
575
  if (r.packageJsonUpdated)
514
576
  console.log("added uc-config to package.json");
577
+ for (const f of r.refreshed)
578
+ console.log(`updated ${f} to v${pkgVersion}`);
579
+ for (const f of r.conflicts)
580
+ console.log(`kept ${f} (edited); wrote ${f}.new. Merge your changes, then delete ${f}.new`);
515
581
  for (const f of r.skipped)
516
582
  console.log(`kept existing ${f}`);
517
- console.log("Next: npm install, then start your coding agent here and ask it to set up your Remote 3.");
583
+ if (o.refreshDocs) {
584
+ if (r.conflicts.length)
585
+ process.exitCode = 2;
586
+ return;
587
+ }
588
+ const interactive = Boolean(process.stdin.isTTY);
589
+ const next = "Next: npm install, then start your coding agent here and ask it to set up your Remote 3.";
590
+ if (!o.connect)
591
+ return console.log(next);
592
+ const name = o.target;
593
+ // 1. Connect (unless already connected).
594
+ let target;
595
+ try {
596
+ target = await readJson(targetFile(name));
597
+ console.log(`Already connected to ${target.host} as "${name}".`);
598
+ }
599
+ catch (e) {
600
+ if (e.code !== "ENOENT")
601
+ throw e;
602
+ }
603
+ if (!target) {
604
+ const host = o.host ??
605
+ (interactive
606
+ ? await question("Remote 3 IP address (blank to skip): ")
607
+ : "");
608
+ if (!host) {
609
+ console.log(`Skipped connecting. Later: npx uc-config connect ${name} --host http://<IP>\n${next}`);
610
+ return;
611
+ }
612
+ try {
613
+ target = await connectTarget(name, host);
614
+ }
615
+ catch (e) {
616
+ console.log(`Could not reach a Remote 3 at ${host}: ${e.message}\n` +
617
+ "Check the IP, that this computer is on the same network, and that the remote is awake (pick it up).\n" +
618
+ `Retry: npx uc-config init --host <IP> (files already created are kept)`);
619
+ process.exitCode = 1;
620
+ return;
621
+ }
622
+ }
623
+ // 2. Authenticate (unless a key is already available).
624
+ if (await hasCredentials(target)) {
625
+ console.log("Already authenticated.");
626
+ return console.log(next);
627
+ }
628
+ const pin = process.env.UC_PIN ??
629
+ (interactive
630
+ ? await hiddenPrompt("Web configurator PIN (Settings → Profile → Web configurator; blank to skip): ")
631
+ : "");
632
+ if (!pin) {
633
+ console.log("Skipped authentication. Run `npx uc-config auth` in this folder when ready.\n" +
634
+ next);
635
+ return;
636
+ }
637
+ try {
638
+ await authenticate(name, pin);
639
+ }
640
+ catch (e) {
641
+ const status = e instanceof ApiError ? e.status : 0;
642
+ console.log(status === 401 || status === 403
643
+ ? "The remote rejected the PIN. Check that the web configurator is enabled and the PIN is current, then run `npx uc-config auth`."
644
+ : status === 400 || status === 409 || status === 422
645
+ ? 'An API key named "uc-config" already exists on the remote. Revoke it in the web configurator, then run `npx uc-config auth`.'
646
+ : `Authentication failed: ${e.message}. Retry with \`npx uc-config auth\`.`);
647
+ process.exitCode = 1;
648
+ return;
649
+ }
650
+ console.log(next);
518
651
  });
519
652
  program
520
653
  .command("diagnose")
package/dist/init.d.ts CHANGED
@@ -1,7 +1,15 @@
1
+ /** True when the file is unedited generated output from any uc-config version. */
2
+ export declare function isPristine(content: string): boolean;
1
3
  export interface InitResult {
2
4
  written: string[];
3
5
  skipped: string[];
4
6
  packageJsonUpdated: boolean;
7
+ /** Generated docs replaced by --refresh-docs. */
8
+ refreshed: string[];
9
+ /** Edited docs kept; the new version was written to `<name>.new`. */
10
+ conflicts: string[];
5
11
  }
6
12
  /** Scaffold a private config workspace. Never overwrites existing files. */
7
- export declare function init(dir: string, version: string): Promise<InitResult>;
13
+ export declare function init(dir: string, version: string, options?: {
14
+ refreshDocs?: boolean;
15
+ }): Promise<InitResult>;
package/dist/init.js CHANGED
@@ -1,5 +1,23 @@
1
1
  import { readFile, writeFile, mkdir } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ const sha256 = (s) => createHash("sha256").update(s).digest("hex");
5
+ const MARKER = /^<!-- uc-config:generated v(\S+) sha256:([0-9a-f]{64}) -->\n/;
6
+ /** Prefix generated docs with a marker so --refresh-docs can detect edits. */
7
+ const stamp = (version, body) => `<!-- uc-config:generated v${version} sha256:${sha256(body)} -->\n${body}`;
8
+ /** Exact output of releases that predate the marker (0.1.0, 0.2.0). */
9
+ const LEGACY = new Set([
10
+ "053dd83c530adb91f58ad52148c9d858cc28864a137b7b5e65c4563338e91b4f",
11
+ "159e847a6a5b6c5cb9ed96f400e8f5d58cd9acae2e68bf666fc3fae2e4ad249b",
12
+ "d71e02c349b769c916d166a6b26cc14d80da4142042fa90564bda85c897a9b07",
13
+ ]);
14
+ /** True when the file is unedited generated output from any uc-config version. */
15
+ export function isPristine(content) {
16
+ const m = MARKER.exec(content);
17
+ if (m)
18
+ return sha256(content.slice(m[0].length)) === m[2];
19
+ return LEGACY.has(sha256(content));
20
+ }
3
21
  const agents = (version) => `# Remote 3 configuration (uc-config)
4
22
 
5
23
  This folder holds the user's Unfolded Circle Remote 3 configuration. It is
@@ -10,11 +28,17 @@ managed with the \`uc-config\` CLI (v${version}). Reference docs ship with the p
10
28
  - node_modules/uc-config/docs/cli.md: every command, recovery
11
29
  - node_modules/uc-config/docs/configuration-authoring.md: config syntax and ownership
12
30
 
13
- ## First run (no .uc/targets/ yet)
31
+ ## First run
32
+
33
+ \`npx uc-config init\` usually already connected and authenticated. Check what
34
+ exists, and skip steps that are done:
14
35
 
15
- 1. \`npx uc-config connect home --host http://<IP>\`. Ask the user for the IP if not given.
16
- 2. Ask the user to run \`npx uc-config auth\` in their own terminal (the web
17
- configurator must be enabled on the remote). Never ask for the PIN in chat.
36
+ 1. No \`.uc/targets/*.json\`: run \`npx uc-config connect home --host http://<IP>\`.
37
+ Ask the user for the IP if not given.
38
+ 2. No \`.uc/credentials.json\` (and no \`UC_API_KEY\` env): ask the user to run
39
+ \`npx uc-config auth\` in their own terminal (the web configurator must be
40
+ enabled on the remote). Never ask for the PIN in chat, and never run \`auth\`
41
+ yourself; it needs a real terminal.
18
42
  3. \`npx uc-config doctor\`
19
43
  4. \`npx uc-config inventory --bindings generated/devices.ts\`
20
44
  5. \`npx uc-config import --out remote.config.ts\`
@@ -49,8 +73,22 @@ the UC Integration Manager (http://<remote>:9999); run diagnose after any update
49
73
 
50
74
  ## Upgrading the tool
51
75
 
52
- \`npm update uc-config\`, then compile and plan. The plan must show 0 operations
53
- before you make any other change.
76
+ 1. \`npm install uc-config@latest\` (not \`npm update\`: it won't cross 0.x minor versions).
77
+ 2. \`npx uc-config init --refresh-docs\` to update this file and CLAUDE.md.
78
+ Files the user edited are kept; the new version is written beside them as \`.new\`.
79
+ 3. Read node_modules/uc-config/CHANGELOG.md for any "Action required" notes.
80
+ 4. \`npx uc-config compile && npx uc-config plan\`. It must show 0 operations
81
+ before you make any other change. If not, the upgrade changed how the config
82
+ is read: show the user the plan and don't apply it.
83
+ `;
84
+ // Claude Code reads CLAUDE.md, not AGENTS.md. Newer releases follow the
85
+ // @-import; the prose line covers releases that don't support imports.
86
+ const claude = `# Remote 3 configuration (uc-config)
87
+
88
+ Read AGENTS.md in this folder before doing anything. It is the authoritative
89
+ guide for working here: setup steps, editing rules and safety limits.
90
+
91
+ @AGENTS.md
54
92
  `;
55
93
  const tsconfig = {
56
94
  compilerOptions: {
@@ -70,12 +108,36 @@ const gitignore = `node_modules/
70
108
  .env.*
71
109
  `;
72
110
  /** Scaffold a private config workspace. Never overwrites existing files. */
73
- export async function init(dir, version) {
111
+ export async function init(dir, version, options = {}) {
74
112
  await mkdir(dir, { recursive: true });
75
113
  const result = {
76
114
  written: [],
77
115
  skipped: [],
78
116
  packageJsonUpdated: false,
117
+ refreshed: [],
118
+ conflicts: [],
119
+ };
120
+ const doc = async (name, contents) => {
121
+ let existing;
122
+ try {
123
+ existing = await readFile(join(dir, name), "utf8");
124
+ }
125
+ catch (e) {
126
+ if (e.code !== "ENOENT")
127
+ throw e;
128
+ }
129
+ if (existing === undefined)
130
+ return put(name, contents);
131
+ if (!options.refreshDocs || existing === contents)
132
+ return void result.skipped.push(name);
133
+ if (isPristine(existing)) {
134
+ await writeFile(join(dir, name), contents);
135
+ result.refreshed.push(name);
136
+ }
137
+ else {
138
+ await writeFile(join(dir, `${name}.new`), contents);
139
+ result.conflicts.push(name);
140
+ }
79
141
  };
80
142
  const put = async (name, contents) => {
81
143
  try {
@@ -119,6 +181,7 @@ export async function init(dir, version) {
119
181
  }
120
182
  await put("tsconfig.json", JSON.stringify(tsconfig, null, 2) + "\n");
121
183
  await put(".gitignore", gitignore);
122
- await put("AGENTS.md", agents(version));
184
+ await doc("AGENTS.md", stamp(version, agents(version)));
185
+ await doc("CLAUDE.md", stamp(version, claude));
123
186
  return result;
124
187
  }
package/docs/cli.md CHANGED
@@ -9,33 +9,38 @@ the workspace's only target if exactly one is connected; otherwise `home`.
9
9
 
10
10
  ## Commands
11
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. |
12
+ | Command | Writes to remote | Purpose |
13
+ | ---------------------------------------------- | ----------------- | ------------------------------------------------------------------------------ |
14
+ | `init [--host ip] [--no-connect]` | API key only | Scaffold a workspace, then prompt for IP and PIN (connect + auth). Rerunnable. |
15
+ | `init --refresh-docs` | no | Update AGENTS.md/CLAUDE.md; edited files are kept and get a `.new` copy. |
16
+ | `connect <name> --host <url>` | no | Record a target (identity, firmware). Rerun after firmware updates. |
17
+ | `auth` | API key only | Exchange the web-configurator PIN (`UC_PIN` or prompt) for an API key. |
18
+ | `doctor` | no | Verify identity and read access to every required endpoint. |
19
+ | `diagnose [--json]` | no | Orphaned entity references, disconnected integrations, suggested fixes. |
20
+ | `inventory [--out f] [--bindings f.ts]` | no | Raw (redacted) remote state; optional typed entity/command bindings. |
21
+ | `import [--out f.ts]` | no | Generate editable config from the live remote. Never overwrites. |
22
+ | `compile [--config f] [--out f]` | no | Evaluate TS config into `.uc/build.json`. Offline. |
23
+ | `plan [--out f] [--prune] [--overwrite-drift]` | no | Three-way diff: source vs last-applied vs live. |
24
+ | `apply <plan> [--adopt-only]` | yes | Execute a saved plan after re-verifying preconditions. |
25
+ | `check` | no | Re-plan and exit 2 if anything differs. |
26
+ | `resume` | maybe | Continue paused setup; reconcile an interrupted apply. |
27
+ | `setup status/respond <key>` | yes | Drive interactive integration/dock setup. |
28
+ | `pairing status/respond <remoteId>` | yes | Bluetooth pairing steps. |
29
+ | `ir learn/capture <emitterId>` | yes | Learn IR codes from a physical remote. |
30
+ | `state adopt <key> <id>` / `forget` / `move` | no | Edit local ownership bindings. |
31
+ | `rollback [--out f]` | no | Build a compensating plan from the last journal. |
32
+ | `backup --out f` | stops intgs | Native full backup. Disruptive; not for routine use. |
33
+ | `api <METHOD> <path> [--data json] [--write]` | only with --write | Raw authenticated Core API call; output redacted. |
33
34
 
34
35
  Exit codes: `0` ok, `1` error, `2` drift/conflicts/deferred/diagnose findings,
35
36
  `3` paused for a human step.
36
37
 
37
38
  ## Authentication
38
39
 
40
+ `init` runs `connect` and `auth` for you when started in a terminal. Without a
41
+ terminal (e.g. run by an agent) it only scaffolds files unless `--host` and
42
+ `UC_PIN` are supplied. Steps already completed are skipped on reruns.
43
+
39
44
  `auth` prompts for the web configurator PIN. Enable the web configurator on the
40
45
  remote first, and approve the key on the remote if asked. Non-interactive
41
46
  options: set `UC_PIN`, or set `UC_API_KEY` to an existing key (`connect
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uc-config",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Configuration-as-code for the Unfolded Circle Remote 3, designed to be driven by coding agents",
6
6
  "license": "MIT",
@@ -36,6 +36,7 @@
36
36
  "examples",
37
37
  "remote.config.example.ts",
38
38
  "README.md",
39
+ "CHANGELOG.md",
39
40
  "LICENSE"
40
41
  ],
41
42
  "engines": {