synthesisui 0.16.22 → 0.16.23

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.
@@ -0,0 +1,98 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { readToken, resolveRegistry } from "../config.js";
4
+ import { readEvents } from "../doctor/ledger.js";
5
+ import { readRequests } from "../doctor/requests.js";
6
+ import { body, section } from "../output.js";
7
+ /**
8
+ * `synthesisui sync` - the ledger travels, because a person said so.
9
+ *
10
+ * The hook writes the ledger and NEVER touches the network; the doctor reads
11
+ * it and never sends it. This command is the one place the record leaves the
12
+ * machine, and it only runs by hand. That split is load-bearing: a check that
13
+ * phones home is a check that gets uninstalled, and the entire governance
14
+ * stack stands on the check staying installed.
15
+ *
16
+ * What it buys: the dashboard answer to the question a design-system lead
17
+ * asks before paying for anything - is my team's drift going up or down? Run
18
+ * it after a session, from CI after a merge, or never; the local record is
19
+ * complete either way.
20
+ *
21
+ * Re-running is free and safe. The server dedupes events on their identity
22
+ * and treats the request queue as a snapshot, so the local files remain the
23
+ * single source of truth.
24
+ */
25
+ export async function sync(opts) {
26
+ const root = resolve(opts.dir ?? process.cwd());
27
+ const base = resolveRegistry(opts.registry);
28
+ const token = await readToken();
29
+ if (!token) {
30
+ console.log(section("Sync"));
31
+ console.log(body("Not logged in. The record stays local until you say so:"));
32
+ console.log(body(" synthesisui login"));
33
+ return;
34
+ }
35
+ // Which system does this project's record belong to? The .lock says.
36
+ const dsDir = join(root, "_synthesisui", "ds");
37
+ let slug = null;
38
+ try {
39
+ const slugs = (await readdir(dsDir, { withFileTypes: true }))
40
+ .filter((e) => e.isDirectory())
41
+ .map((e) => e.name);
42
+ for (const s of slugs) {
43
+ const raw = await readFile(join(dsDir, s, ".lock"), "utf8").catch(() => "");
44
+ try {
45
+ const lock = JSON.parse(raw);
46
+ // An adopted system is described, not owned on the platform - there is
47
+ // no dashboard row to sync into (yet).
48
+ if (lock.slug && !lock.adopted) {
49
+ slug = lock.slug;
50
+ break;
51
+ }
52
+ }
53
+ catch {
54
+ // unreadable lock - try the next
55
+ }
56
+ }
57
+ }
58
+ catch {
59
+ // no _synthesisui at all
60
+ }
61
+ if (!slug) {
62
+ console.log(section("Sync"));
63
+ console.log(body("No installed system here, so there is nowhere to sync to."));
64
+ return;
65
+ }
66
+ const events = await readEvents(root);
67
+ const requests = await readRequests(root);
68
+ if (events.length === 0 && requests.length === 0) {
69
+ console.log(section("Sync"));
70
+ console.log(body("Nothing recorded yet. The record fills as the hook checks writes and doctor runs."));
71
+ return;
72
+ }
73
+ const res = await fetch(`${base}/api/ledger`, {
74
+ method: "POST",
75
+ headers: {
76
+ "content-type": "application/json",
77
+ Authorization: `Bearer ${token}`,
78
+ },
79
+ body: JSON.stringify({ slug, events, requests }),
80
+ }).catch(() => null);
81
+ if (!res) {
82
+ console.log(section("Sync"));
83
+ console.log(body("Could not reach the registry. The record is safe locally."));
84
+ return;
85
+ }
86
+ if (!res.ok) {
87
+ const err = (await res.json().catch(() => ({})));
88
+ console.log(section("Sync"));
89
+ console.log(body(err.message ?? `The registry answered ${res.status}.`));
90
+ return;
91
+ }
92
+ const out = (await res.json());
93
+ console.log(section("Sync"));
94
+ console.log(body(`${out.eventsReceived} checks sent, ${out.eventsNew} new. ${out.requestsNow} open request${out.requestsNow === 1 ? "" : "s"}.`));
95
+ console.log("");
96
+ console.log(body(`The record lives on your system's overview:`));
97
+ console.log(body(` ${base}/dashboard/mine/${slug}`));
98
+ }
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { login } from "./commands/login.js";
15
15
  import { mcp } from "./commands/mcp.js";
16
16
  import { refit } from "./commands/refit.js";
17
17
  import { request } from "./commands/request.js";
18
+ import { sync } from "./commands/sync.js";
18
19
  import { template } from "./commands/template.js";
19
20
  import { upgrade } from "./commands/upgrade.js";
20
21
  import { use } from "./commands/use.js";
@@ -331,6 +332,9 @@ async function main() {
331
332
  case "clean":
332
333
  await clean({ dir, force: flags.force === true });
333
334
  break;
335
+ case "sync":
336
+ await sync({ dir, registry });
337
+ break;
334
338
  case "request": {
335
339
  // `synthesisui request` lists; `request component --name x --for "..."`
336
340
  // files; `request --done <id>` closes. The agent's front door is the MCP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.22",
3
+ "version": "0.16.23",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {