synthesisui 0.16.21 → 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.
- package/dist/claude-md.js +11 -5
- package/dist/commands/doctor.js +28 -0
- package/dist/commands/mcp.js +67 -0
- package/dist/commands/request.js +62 -0
- package/dist/commands/sync.js +98 -0
- package/dist/doctor/requests.js +74 -0
- package/dist/index.js +22 -0
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -114,10 +114,13 @@ async function readManifest(projectRoot, ds) {
|
|
|
114
114
|
*/
|
|
115
115
|
const REFUSAL = `
|
|
116
116
|
|
|
117
|
-
If it reports a value with no token, do NOT invent one.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
If it reports a value with no token, do NOT invent one. FILE it: the
|
|
118
|
+
\`request_token\` MCP tool, or \`npx synthesisui@latest request token --value
|
|
119
|
+
"<value>" --name "<what you would call it>" --for "<what it is for>"\`. A person
|
|
120
|
+
decides from that queue. An agent that quiets the report by adding a token is
|
|
121
|
+
growing the system unsupervised, which is worse than the drift because it looks
|
|
122
|
+
solved - and a refusal that lives only in this chat evaporates, which wastes the
|
|
123
|
+
best reasoning you produce.`;
|
|
121
124
|
/**
|
|
122
125
|
* TWO THINGS AN AGENT GOT WRONG ON 28/07, BOTH CHEAP TO SAY OUT LOUD.
|
|
123
126
|
*
|
|
@@ -288,7 +291,10 @@ If an override is ignored, that component predates the resolver: regenerate it w
|
|
|
288
291
|
\`npx synthesisui@latest component <slug> <name>\` rather than reaching for \`!\`.
|
|
289
292
|
|
|
290
293
|
Only write something new when nothing in the manifest covers the purpose - and when you do,
|
|
291
|
-
say which entry you considered and why it did not fit
|
|
294
|
+
say which entry you considered and why it did not fit, then FILE it while the reasoning is
|
|
295
|
+
still yours: the \`request_component\` MCP tool, or \`npx synthesisui@latest request component
|
|
296
|
+
--name "<name>" --for "<the use case>" --considered "<entries and why not>"\`. That queue is
|
|
297
|
+
what the system's author works from - a refusal said only in chat evaporates. To review a
|
|
292
298
|
component, create an isolated sample page (e.g. \`app/synthesisui-samples/<component>/\`) - do not
|
|
293
299
|
apply it to real production pages unless asked.${selfCheck}`;
|
|
294
300
|
const locale = await readInterfaceLanguage(projectRoot);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { findDivergences } from "../doctor/coherence.js";
|
|
|
4
4
|
import { findFrozenBindings } from "../doctor/frozen.js";
|
|
5
5
|
import { appendEvent, readEvents, summarize } from "../doctor/ledger.js";
|
|
6
6
|
import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
|
|
7
|
+
import { readRequests } from "../doctor/requests.js";
|
|
7
8
|
import { diagnose, scanSource, siblingTokens, } from "../doctor/scan.js";
|
|
8
9
|
import { findSelfConflicts, forbiddenProps, isReset, propMatchesLabel, } from "../doctor/self-conflict.js";
|
|
9
10
|
import { buildTable, EMPTY_TABLE, nearestToken, } from "../doctor/tokens.js";
|
|
@@ -563,6 +564,33 @@ export async function doctor(opts) {
|
|
|
563
564
|
}
|
|
564
565
|
}
|
|
565
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* RECUSA → PEDIDO, the person's side of the queue.
|
|
569
|
+
*
|
|
570
|
+
* Every request here was written by the agent at the moment it needed
|
|
571
|
+
* something the system refused to invent - which makes this list the best
|
|
572
|
+
* possible answer to "what should this system have next?": the use case and
|
|
573
|
+
* the considered-and-rejected entries arrive already argued, by the party
|
|
574
|
+
* that actually hit the gap.
|
|
575
|
+
*/
|
|
576
|
+
if (hasSystem && scopes.length === 0) {
|
|
577
|
+
const requests = await readRequests(root);
|
|
578
|
+
if (requests.length > 0) {
|
|
579
|
+
console.log("");
|
|
580
|
+
console.log(section("What your agent asked for"));
|
|
581
|
+
for (const r of requests.slice(0, 8)) {
|
|
582
|
+
console.log(body(` ${r.id} ${r.kind === "token" ? `token ${r.value} as ${r.name}` : `component "${r.name}"`}`));
|
|
583
|
+
console.log(body(` for: ${r.purpose}`));
|
|
584
|
+
if (r.considered)
|
|
585
|
+
console.log(body(` considered: ${r.considered}`));
|
|
586
|
+
}
|
|
587
|
+
if (requests.length > 8) {
|
|
588
|
+
console.log(body(` and ${requests.length - 8} more.`));
|
|
589
|
+
}
|
|
590
|
+
console.log("");
|
|
591
|
+
console.log(body("Author it in the studio, or close it: synthesisui request --done <id>"));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
566
594
|
/**
|
|
567
595
|
* Its own section, above drift, because it is a worse problem wearing a
|
|
568
596
|
* better disguise.
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { relative, resolve } from "node:path";
|
|
3
|
+
import { fileRequest } from "../doctor/requests.js";
|
|
3
4
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
4
5
|
import { nearestToken, tokenFor } from "../doctor/tokens.js";
|
|
5
6
|
import { component } from "./component.js";
|
|
@@ -78,6 +79,52 @@ const TOOLS = [
|
|
|
78
79
|
required: ["name"],
|
|
79
80
|
},
|
|
80
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: "request_component",
|
|
84
|
+
description: "File a component request when nothing in the catalogue covers what you need. This is the OTHER HALF of the refusal rule: you already say which entry you considered and why it did not fit - said in chat, that reasoning evaporates; filed here, it becomes the queue the system's author works from. File it at the moment you build the workaround, while the reasoning is still yours.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
name: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "What you would call it, kebab-case: keyboard-hint",
|
|
91
|
+
},
|
|
92
|
+
purpose: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "What you needed it for, concretely: 'a drawn key cap (⌘K) in a shortcut legend'.",
|
|
95
|
+
},
|
|
96
|
+
considered: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Which catalogue entries you considered and why each did not fit.",
|
|
99
|
+
},
|
|
100
|
+
file: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Where your workaround lives, so authoring can replace it.",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
required: ["name", "purpose"],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "request_token",
|
|
110
|
+
description: "File a token request for a value this system has no name for. Do NOT invent a token - an agent that quiets a report by growing the system is worse than the drift. Filing is the correct move: the value, what you would call it, and what it is for, decided later by a person.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
value: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "The concrete value: #ffffff, 4.5rem…",
|
|
117
|
+
},
|
|
118
|
+
name: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "What you would call it: color-semantic-on-primary",
|
|
121
|
+
},
|
|
122
|
+
purpose: { type: "string", description: "What it is for." },
|
|
123
|
+
file: { type: "string", description: "Where the need arose." },
|
|
124
|
+
},
|
|
125
|
+
required: ["value", "name", "purpose"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
81
128
|
];
|
|
82
129
|
async function checkFile(root, path) {
|
|
83
130
|
const { table } = await loadSystem(root);
|
|
@@ -162,6 +209,26 @@ async function callTool(root, name, args) {
|
|
|
162
209
|
return text(await listComponents(root));
|
|
163
210
|
case "add_component":
|
|
164
211
|
return text(await addComponent(root, String(args.name ?? "")));
|
|
212
|
+
case "request_component": {
|
|
213
|
+
const r = await fileRequest(root, {
|
|
214
|
+
kind: "component",
|
|
215
|
+
name: String(args.name ?? ""),
|
|
216
|
+
purpose: String(args.purpose ?? ""),
|
|
217
|
+
considered: args.considered ? String(args.considered) : undefined,
|
|
218
|
+
file: args.file ? String(args.file) : undefined,
|
|
219
|
+
});
|
|
220
|
+
return text(`Filed as ${r.id}. It shows up in \`synthesisui doctor\` until a person authors it or closes it - keep building with your workaround, and say in your summary that the request exists.`);
|
|
221
|
+
}
|
|
222
|
+
case "request_token": {
|
|
223
|
+
const r = await fileRequest(root, {
|
|
224
|
+
kind: "token",
|
|
225
|
+
name: String(args.name ?? ""),
|
|
226
|
+
purpose: String(args.purpose ?? ""),
|
|
227
|
+
value: String(args.value ?? ""),
|
|
228
|
+
file: args.file ? String(args.file) : undefined,
|
|
229
|
+
});
|
|
230
|
+
return text(`Filed as ${r.id}. Do not add the token yourself - the request shows up in \`synthesisui doctor\` for a person to decide.`);
|
|
231
|
+
}
|
|
165
232
|
default:
|
|
166
233
|
return text(`No tool named ${name}.`, true);
|
|
167
234
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { closeRequest, fileRequest, readRequests } from "../doctor/requests.js";
|
|
3
|
+
import { body, section } from "../output.js";
|
|
4
|
+
/**
|
|
5
|
+
* `synthesisui request` - the queue of what the agent needed and was refused.
|
|
6
|
+
*
|
|
7
|
+
* The MCP tools are the front door (the agent files at the moment of the
|
|
8
|
+
* workaround, reasoning still warm). This command is the fallback for agents
|
|
9
|
+
* without the MCP connected, and the PERSON's half: listing the queue and
|
|
10
|
+
* closing what was authored or declined.
|
|
11
|
+
*
|
|
12
|
+
* Closing is deliberately explicit. A request nobody got to is still a
|
|
13
|
+
* request; nothing here expires.
|
|
14
|
+
*/
|
|
15
|
+
export async function request(opts) {
|
|
16
|
+
const root = resolve(opts.dir ?? process.cwd());
|
|
17
|
+
if (opts.done) {
|
|
18
|
+
const closed = await closeRequest(root, opts.done);
|
|
19
|
+
console.log(closed
|
|
20
|
+
? `✓ ${opts.done} closed.`
|
|
21
|
+
: `Nothing here has the id ${opts.done} - synthesisui request --list shows the queue.`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (opts.list || !opts.kind) {
|
|
25
|
+
const all = await readRequests(root);
|
|
26
|
+
console.log(section("What your agent asked for"));
|
|
27
|
+
if (all.length === 0) {
|
|
28
|
+
console.log(body("Nothing yet. When the agent needs something the system does not have, it files the gap here instead of inventing."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const r of all) {
|
|
32
|
+
console.log(body(` ${r.id} ${r.kind === "token" ? `token ${r.value} as ${r.name}` : `component "${r.name}"`}`));
|
|
33
|
+
console.log(body(` for: ${r.purpose}`));
|
|
34
|
+
if (r.considered)
|
|
35
|
+
console.log(body(` considered: ${r.considered}`));
|
|
36
|
+
if (r.file)
|
|
37
|
+
console.log(body(` workaround in: ${r.file}`));
|
|
38
|
+
}
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(body("Close one: synthesisui request --done <id>"));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (opts.kind !== "component" && opts.kind !== "token") {
|
|
44
|
+
console.log(`Unknown kind "${opts.kind}" - component or token.`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!opts.name || !opts.purpose || (opts.kind === "token" && !opts.value)) {
|
|
48
|
+
console.log(opts.kind === "token"
|
|
49
|
+
? "A token request needs --value, --name and --for."
|
|
50
|
+
: "A component request needs --name and --for.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const r = await fileRequest(root, {
|
|
54
|
+
kind: opts.kind,
|
|
55
|
+
name: opts.name,
|
|
56
|
+
purpose: opts.purpose,
|
|
57
|
+
considered: opts.considered,
|
|
58
|
+
value: opts.value,
|
|
59
|
+
file: opts.file,
|
|
60
|
+
});
|
|
61
|
+
console.log(`✓ Filed as ${r.id}. It shows up in synthesisui doctor.`);
|
|
62
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* RECUSA → PEDIDO: the refusal becomes the request queue.
|
|
5
|
+
*
|
|
6
|
+
* The contract's proudest rule is the refusal: when the agent needs something
|
|
7
|
+
* the system does not have, it must not invent - it names the gap and stops.
|
|
8
|
+
* And then the naming EVAPORATES. It happens in a chat message: "I considered
|
|
9
|
+
* ds-readout (it is a HUD instrument, not a key legend) and composed with
|
|
10
|
+
* Row + Text instead" - which is a complete, well-argued component request,
|
|
11
|
+
* written by the party that actually needed the component, read once by one
|
|
12
|
+
* person, and gone.
|
|
13
|
+
*
|
|
14
|
+
* Nobody authors components in a vacuum. The best possible input for "what
|
|
15
|
+
* should this system have next?" is the list of things its own agent needed
|
|
16
|
+
* and was refused, each with the use case and the considered-and-rejected
|
|
17
|
+
* alternatives already written down. That list was being generated every day
|
|
18
|
+
* and stored nowhere.
|
|
19
|
+
*
|
|
20
|
+
* Same file discipline as the ledger, and the same locality argument: one
|
|
21
|
+
* JSONL inside `_synthesisui/`, nothing leaves the machine, committing it is
|
|
22
|
+
* how a team shares a queue.
|
|
23
|
+
*/
|
|
24
|
+
export const REQUESTS_FILE = "requests.jsonl";
|
|
25
|
+
const path = (root) => join(root, "_synthesisui", REQUESTS_FILE);
|
|
26
|
+
/** Stable-enough id from content: 6 chars, collision-safe at queue scale. */
|
|
27
|
+
function idOf(kind, name, at) {
|
|
28
|
+
let h = 0;
|
|
29
|
+
for (const c of `${kind}:${name}:${at}`)
|
|
30
|
+
h = (h * 31 + c.charCodeAt(0)) | 0;
|
|
31
|
+
return (h >>> 0).toString(36).slice(0, 6).padStart(6, "0");
|
|
32
|
+
}
|
|
33
|
+
export async function fileRequest(root, req) {
|
|
34
|
+
const at = req.at ?? new Date().toISOString();
|
|
35
|
+
const full = { ...req, at, id: idOf(req.kind, req.name, at) };
|
|
36
|
+
try {
|
|
37
|
+
await appendFile(path(root), `${JSON.stringify(full)}\n`, "utf8");
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A queue that can break the caller is a queue that gets bypassed. The
|
|
41
|
+
// request is still returned, so the tool's answer can carry it.
|
|
42
|
+
}
|
|
43
|
+
return full;
|
|
44
|
+
}
|
|
45
|
+
export async function readRequests(root) {
|
|
46
|
+
const raw = await readFile(path(root), "utf8").catch(() => "");
|
|
47
|
+
if (!raw)
|
|
48
|
+
return [];
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const line of raw.split("\n")) {
|
|
51
|
+
if (!line.trim())
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const r = JSON.parse(line);
|
|
55
|
+
if (r?.id && r.name && (r.kind === "component" || r.kind === "token"))
|
|
56
|
+
out.push(r);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// A corrupt line loses itself, not the queue.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/** Closing is a PERSON's act - authored it, or decided against it. Either way
|
|
65
|
+
* the queue shrinks by explicit hand, never by timeout: a request nobody got
|
|
66
|
+
* to is still a request. */
|
|
67
|
+
export async function closeRequest(root, id) {
|
|
68
|
+
const all = await readRequests(root);
|
|
69
|
+
const keep = all.filter((r) => r.id !== id);
|
|
70
|
+
if (keep.length === all.length)
|
|
71
|
+
return false;
|
|
72
|
+
await writeFile(path(root), keep.length ? `${keep.map((r) => JSON.stringify(r)).join("\n")}\n` : "", "utf8");
|
|
73
|
+
return true;
|
|
74
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,8 @@ import { list } from "./commands/list.js";
|
|
|
14
14
|
import { login } from "./commands/login.js";
|
|
15
15
|
import { mcp } from "./commands/mcp.js";
|
|
16
16
|
import { refit } from "./commands/refit.js";
|
|
17
|
+
import { request } from "./commands/request.js";
|
|
18
|
+
import { sync } from "./commands/sync.js";
|
|
17
19
|
import { template } from "./commands/template.js";
|
|
18
20
|
import { upgrade } from "./commands/upgrade.js";
|
|
19
21
|
import { use } from "./commands/use.js";
|
|
@@ -330,6 +332,26 @@ async function main() {
|
|
|
330
332
|
case "clean":
|
|
331
333
|
await clean({ dir, force: flags.force === true });
|
|
332
334
|
break;
|
|
335
|
+
case "sync":
|
|
336
|
+
await sync({ dir, registry });
|
|
337
|
+
break;
|
|
338
|
+
case "request": {
|
|
339
|
+
// `synthesisui request` lists; `request component --name x --for "..."`
|
|
340
|
+
// files; `request --done <id>` closes. The agent's front door is the MCP
|
|
341
|
+
// tool - this is the fallback and the person's half.
|
|
342
|
+
await request({
|
|
343
|
+
dir,
|
|
344
|
+
kind: args[0],
|
|
345
|
+
name: typeof flags.name === "string" ? flags.name : undefined,
|
|
346
|
+
purpose: typeof flags.for === "string" ? flags.for : undefined,
|
|
347
|
+
considered: typeof flags.considered === "string" ? flags.considered : undefined,
|
|
348
|
+
value: typeof flags.value === "string" ? flags.value : undefined,
|
|
349
|
+
file: typeof flags.file === "string" ? flags.file : undefined,
|
|
350
|
+
done: typeof flags.done === "string" ? flags.done : undefined,
|
|
351
|
+
list: flags.list === true,
|
|
352
|
+
});
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
333
355
|
case "advise": {
|
|
334
356
|
const valueProp = args.join(" ").trim();
|
|
335
357
|
if (!valueProp) {
|
package/package.json
CHANGED