youiyoux 0.1.2 → 0.1.3

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.
@@ -8,7 +8,7 @@ import {
8
8
  libraryOfPackage,
9
9
  packageOf,
10
10
  playwrightCli
11
- } from "./chunk-FUB7B3VK.js";
11
+ } from "./chunk-74KBRI2H.js";
12
12
  import {
13
13
  candidateSource,
14
14
  checkpointSqlite,
@@ -29,7 +29,7 @@ import {
29
29
  showFromCatalog,
30
30
  storeLocation,
31
31
  syncIndex
32
- } from "./chunk-UHPK62Q5.js";
32
+ } from "./chunk-UEB6MJDX.js";
33
33
  import {
34
34
  BASELINE_FILE,
35
35
  DEFAULT_EXCLUDE,
@@ -41,6 +41,8 @@ import {
41
41
  buildInventory,
42
42
  buildTheme,
43
43
  buildTokens,
44
+ captureScreens,
45
+ captureText,
44
46
  checkCapabilities,
45
47
  compareParity,
46
48
  compareToBaseline,
@@ -76,7 +78,7 @@ import {
76
78
  substituteVars,
77
79
  vendoredPath,
78
80
  whereUsed
79
- } from "./chunk-3ZHGONNA.js";
81
+ } from "./chunk-L5BV4PI7.js";
80
82
  import {
81
83
  within
82
84
  } from "./chunk-S7UV34H5.js";
@@ -91,6 +93,7 @@ import {
91
93
  OutputConfig,
92
94
  ProjectConfig,
93
95
  ScanReport,
96
+ copyleft,
94
97
  executionLabel,
95
98
  findFiles,
96
99
  getSystem,
@@ -107,7 +110,7 @@ import {
107
110
  sha256,
108
111
  shortHash,
109
112
  writeFileAtomic
110
- } from "./chunk-ZLRFD5JI.js";
113
+ } from "./chunk-C3TSTYP7.js";
111
114
  import {
112
115
  __toESM
113
116
  } from "./chunk-E4HNK7VF.js";
@@ -140,7 +143,7 @@ var import_yaml3 = __toESM(require_dist(), 1);
140
143
  import { execFile as execFile6 } from "node:child_process";
141
144
  import { existsSync as existsSync4 } from "node:fs";
142
145
  import { appendFile as appendFile2, readFile as readFile8 } from "node:fs/promises";
143
- import { join as join22, relative as relative9, resolve as resolve12 } from "node:path";
146
+ import { basename as basename3, join as join22, relative as relative9, resolve as resolve12 } from "node:path";
144
147
  import { parseArgs, promisify as promisify6 } from "node:util";
145
148
 
146
149
  // ../cli/src/check.ts
@@ -158,7 +161,7 @@ import { join as join2, relative, resolve } from "node:path";
158
161
 
159
162
  // ../cli/src/command.ts
160
163
  var COMMAND = "youiyoux";
161
- var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "detect", "build", "parity", "schema", "inventory", "help"];
164
+ var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "init", "sync", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "help"];
162
165
 
163
166
  // ../cli/src/agents.ts
164
167
  var import_yaml = __toESM(require_dist(), 1);
@@ -231,6 +234,7 @@ function agentsBlock(project2) {
231
234
  `- Never edit files marked \`${GENERATED_MARK}\` (\`${config.outputs.dir}/*\`, ${config.agents.designMd}, the youiyoux skill). Change the design language and run \`youiyoux sync\`.`,
232
235
  `- Owned component files in \`${config.componentsDir}\` may be edited; \`youiyoux sync\` 3-way merges upstream changes into them and reports drift.`,
233
236
  "- After changing a component, call `ui_render` with its file (or run `youiyoux render <file>`) and look at the images: light and dark, desktop and phone, its accessibility issues and the diff against the base. Fix what it reports.",
237
+ "- People pin comments and change requests to components on captured screens. Before UI work, call `requests_list` (or run `youiyoux requests`); after making a requested change, capture its screen again (`youiyoux screens capture --base <app> --route <route>`) and call `request_addressed` with what you changed (file:line). Ask with `request_reply` when one is unclear. A person closes requests, never an agent.",
234
238
  "- Before you finish, run `youiyoux check`; it must report 0 errors.",
235
239
  diffInstruction(project2.root),
236
240
  "- To change the System itself (a token, a prop, a rule), run `youiyoux plan` (or `system_plan`) and propose it. Agents never approve, publish or merge design changes.",
@@ -2047,7 +2051,7 @@ function renderDiscoverSearch(result) {
2047
2051
  const width = Math.min(40, Math.max(...result.hits.map((h) => (h.source.ref ?? h.name).length)));
2048
2052
  const rows = result.hits.flatMap((h) => {
2049
2053
  const s = h.signals;
2050
- const facts = [h.license === "unknown" ? style.warn("licence unknown") : h.license, s.via ? `${compact(s.via.weeklyDownloads)}/wk via ${s.via.package}` : `${compact(s.weeklyDownloads)}/wk`, `${compact(s.stars)}\u2605`, ...s.archived ? [style.error("archived")] : []].join(" \xB7 ");
2054
+ const facts = [h.license === "unknown" ? style.warn("licence unknown") : copyleft(h.license) ? style.warn(`${h.license} (copyleft)`) : h.license, s.via ? `${compact(s.via.weeklyDownloads)}/wk via ${s.via.package}` : `${compact(s.weeklyDownloads)}/wk`, `${compact(s.stars)}\u2605`, ...s.archived ? [style.error("archived")] : []].join(" \xB7 ");
2051
2055
  return [` ${padEnd(truncate(h.source.ref ?? h.name, 40), width)} ${truncate(h.summary, 72)}`, ` ${" ".repeat(width)} ${style.dim(facts)} ${h.install ? h.install.command : ""}`];
2052
2056
  });
2053
2057
  const from = result.offset ?? 0;
@@ -2087,7 +2091,7 @@ import { homedir, userInfo } from "node:os";
2087
2091
  import { join as join12, resolve as resolve6 } from "node:path";
2088
2092
  async function swapImpact(candidate, family) {
2089
2093
  try {
2090
- const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-4KFNL3MY.js");
2094
+ const { discoverIndexPath: discoverIndexPath2, sharedStore: sharedStore2 } = await import("./src-YUC5PIDK.js");
2091
2095
  const path = discoverIndexPath2();
2092
2096
  if (!process.env.DATABASE_URL && !existsSync(path)) return null;
2093
2097
  const impact = await (await sharedStore2(path, process.env, { readOnly: true })).swapImpact(candidate, family);
@@ -2098,7 +2102,7 @@ async function swapImpact(candidate, family) {
2098
2102
  }
2099
2103
  async function libraryLicence(id) {
2100
2104
  try {
2101
- const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-4KFNL3MY.js");
2105
+ const { discoverCacheDir: discoverCacheDir2, findListing } = await import("./src-YUC5PIDK.js");
2102
2106
  const { entry } = await findListing(discoverCacheDir2(void 0, process.env), id);
2103
2107
  return entry.id === id ? entry.license : null;
2104
2108
  } catch {
@@ -2106,7 +2110,7 @@ async function libraryLicence(id) {
2106
2110
  }
2107
2111
  }
2108
2112
  async function startRuleEditor(options) {
2109
- const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-7ENJHYG4.js");
2113
+ const { FileStore, RuleWorkflow, serveWorkflow } = await import("./src-U5NTDPUX.js");
2110
2114
  const projectRoot = resolve6(options.project);
2111
2115
  const load2 = { ...options.config ? { configPath: resolve6(options.config) } : {}, ...options.design ? { designRoot: resolve6(options.design) } : {}, ...options.root ? { productRoot: resolve6(options.root) } : {} };
2112
2116
  const project2 = await loadProject(projectRoot, load2);
@@ -2160,6 +2164,44 @@ async function runHub(toolRoot, options) {
2160
2164
  watcher?.kill("SIGTERM");
2161
2165
  }
2162
2166
  }
2167
+ function previewsPlan(toolRoot, options) {
2168
+ const web = join13(toolRoot, "apps/web");
2169
+ const registry = options.registry ?? "@shadcn";
2170
+ const args = [join13(web, "scripts/capture-discover-thumbs.ts"), "--hub", `http://localhost:${options.port ?? DEFAULT_HUB_PORT}`];
2171
+ if (registry !== "all") args.push("--registry", registry);
2172
+ for (const key2 of ["role", "limit"]) if (options[key2]) args.push(`--${key2}`, options[key2]);
2173
+ if (options.cache) args.push("--cache", resolve7(options.cache));
2174
+ if (options.force) args.push("--force");
2175
+ return { web, args };
2176
+ }
2177
+ async function runPreviews(toolRoot, options) {
2178
+ const plan = previewsPlan(toolRoot, options);
2179
+ await access4(join13(plan.web, "node_modules/playwright-core")).catch(() => {
2180
+ throw new Error(`the hub's dependencies are not installed: run \`pnpm install\` in ${toolRoot}`);
2181
+ });
2182
+ return run6(plan.args, plan.web, {});
2183
+ }
2184
+
2185
+ // ../cli/src/requests.ts
2186
+ function cliNext(brief) {
2187
+ return brief.next.replace(/call request_addressed with what you changed \(file:line\)/, `run youiyoux requests addressed ${brief.id} --message "<what you changed, with file:line>"`).replace(/request_reply/g, `youiyoux requests reply ${brief.id} --message "\u2026"`);
2188
+ }
2189
+ function renderBrief(brief, full = false) {
2190
+ const lines = [
2191
+ `${style.bold(brief.id)} ${brief.kind} ${brief.status === "open" ? style.warn(brief.status) : style.dim(brief.status)} ${brief.route} ${brief.component}${brief.lookalike ? style.dim(" (hand-built lookalike)") : ""}${brief.at ? ` ${brief.at}` : ""}`,
2192
+ ` \u201C${brief.ask}\u201D ${style.dim(`\xB7 ${brief.from} \xB7 ${brief.scope}`)}`
2193
+ ];
2194
+ if (brief.picture) lines.push(` ${style.dim("picture")} ${brief.picture}${brief.box ? style.dim(` (box ${brief.box.x},${brief.box.y} ${brief.box.width}\xD7${brief.box.height} px)`) : ""}`);
2195
+ for (const entry of full ? brief.thread : brief.thread.slice(-2)) lines.push(` ${style.dim("\xB7")} ${entry}`);
2196
+ if (!full && brief.thread.length > 2) lines.push(style.dim(` \u2026 ${brief.thread.length - 2} earlier in the thread: youiyoux requests show ${brief.id}`));
2197
+ lines.push(` ${style.dim("next")} ${cliNext(brief)}`);
2198
+ return lines.join("\n");
2199
+ }
2200
+ function renderRequests(briefs, status, unreadable) {
2201
+ const lines = briefs.length ? briefs.map((b) => renderBrief(b)) : [`No ${status === "all" ? "" : `${status} `}requests on the screens.`];
2202
+ for (const u of unreadable) lines.push(style.error(`\u2716 ${u.file} is not a youi.request/v1: ${u.reason}`));
2203
+ return lines.join("\n\n");
2204
+ }
2163
2205
 
2164
2206
  // ../cli/src/index-store.ts
2165
2207
  import { appendFile, mkdir as mkdir3, readFile as readFile4, stat, rename, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
@@ -2784,7 +2826,7 @@ function scaleTokens(index, file, usage = { count: /* @__PURE__ */ new Map(), by
2784
2826
  var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2785
2827
  async function reportTokens(module, tokens, usage) {
2786
2828
  if (isShowcase(module.file) || module.lang !== "css" && module.jsx.length === 0) return [];
2787
- const { parseModule: parseModule2 } = await import("./src-B742AYFC.js");
2829
+ const { parseModule: parseModule2 } = await import("./src-ZS76UD7E.js");
2788
2830
  let source = withoutVariables(module.source);
2789
2831
  if (module.lang !== "css") {
2790
2832
  source = maskFunctions(source, /(?:repeating-)?(?:linear|radial|conic)-gradient\s*\(/gi);
@@ -4276,6 +4318,15 @@ ${style.bold("Anywhere")} ${style.dim("no design system needed")}
4276
4318
  <first project>/.youi/inventory.json unless --out, and records the scan
4277
4319
  (project, commit, time, counts, families) in the local index for trends
4278
4320
  inventory --history every recorded scan per project, newest first, with what changed
4321
+ requests [--status open|addressed|closed|all] [--route r] [--json]
4322
+ comments and change requests people pinned to components on captured
4323
+ screens: what is asked, the file:line, the picture, and what to do next
4324
+ requests show <id> | reply <id> --message <text> | addressed <id> --message <what you changed>
4325
+ read one, reply, or mark it done (as an agent; a person closes it in the hub)
4326
+ screens capture --base <url> [--route r] [--project dir] [--out dir] [--storage-state file] [--json]
4327
+ photograph the routes in <project>/.youi/screens/screens.json, light and
4328
+ dark, from the app running in development at <url>, and place each
4329
+ component on them with the line that rendered it (for youiyoux ui)
4279
4330
  render [file|component\u2026] [--base ref] [--json]
4280
4331
  see a component you changed: its looks drawn with the project's own code
4281
4332
  and CSS in headless Chromium, light and dark at 1280 and 390 px, axe's
@@ -4347,6 +4398,9 @@ ${style.bold("Discover")} ${style.dim("UI components by need, across shadcn reg
4347
4398
  and facets (role, source, licence, headless/styled); --all also lists
4348
4399
  every other registry, kept apart from the default search
4349
4400
  discover show <id|ref> [--source] [--json] one candidate: provenance, licence evidence, dependencies
4401
+ discover previews [--port n] [--registry @shadcn|all] [--role r] [--limit n] [--force]
4402
+ photograph candidates in your theme for the hub's gallery, from a hub
4403
+ running on your project (youiyoux ui --project <dir>)
4350
4404
  (cache: --cache, else YOUI_DISCOVER_CACHE, else ~/.cache/youiyoux/discover)
4351
4405
 
4352
4406
  ${style.bold("Index")} ${style.dim("the local store of derived data: rebuildable, never the authority")}
@@ -4439,6 +4493,10 @@ var OPTIONS = {
4439
4493
  license: { type: "string" },
4440
4494
  icons: { type: "boolean" },
4441
4495
  source: { type: "boolean" },
4496
+ registry: { type: "string" },
4497
+ "storage-state": { type: "string" },
4498
+ message: { type: "string" },
4499
+ status: { type: "string" },
4442
4500
  "max-registries": { type: "string" },
4443
4501
  offset: { type: "string" },
4444
4502
  history: { type: "boolean", default: false },
@@ -4877,7 +4935,7 @@ async function cmdUi(values) {
4877
4935
  async function cmdDetect(values, positionals) {
4878
4936
  const target = detectTarget(positionals);
4879
4937
  if (!target) throw new UsageError("youiyoux detect <repo-path> or youiyoux detect <url\u2026> [--expose] (one repo, or one or more URLs; not both)");
4880
- const { repoReport, siteReport, writeReport } = await import("./src-VEHGMKYV.js");
4938
+ const { repoReport, siteReport, writeReport } = await import("./src-DS43LCRA.js");
4881
4939
  const out = resolve12(values.out ?? ".youi/detect");
4882
4940
  const report = target.kind === "repo" ? await repoReport(target.path) : await siteReport(target.urls, { out, expose: values.expose, ...process.env.YOUI_CHROMIUM ? { executablePath: process.env.YOUI_CHROMIUM } : {} });
4883
4941
  const file = await writeReport(report, out);
@@ -4903,13 +4961,68 @@ async function cmdRender(values, positionals, io) {
4903
4961
  return 1;
4904
4962
  }
4905
4963
  }
4964
+ async function cmdRequests(values, positionals) {
4965
+ const usage = "youiyoux requests [--status open|addressed|closed|all] [--route r] or youiyoux requests show <id> | reply <id> --message <text> | addressed <id> --message <what you changed>";
4966
+ const [action, id, ...rest] = positionals;
4967
+ if (rest.length > 0) throw new UsageError(usage);
4968
+ const { RequestStore, requestBrief } = await import("./src-U5NTDPUX.js");
4969
+ const project2 = await loadProject(resolve12(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
4970
+ const root = project2?.root ?? resolve12(values.root ?? values.project ?? process.cwd());
4971
+ const store = new RequestStore(root, project2?.config.id ?? basename3(root));
4972
+ const agent = { kind: "agent", name: "youiyoux CLI" };
4973
+ const one = (request) => {
4974
+ const brief = requestBrief(request, root);
4975
+ if (values.json) json(brief);
4976
+ else print(renderBrief(brief, true));
4977
+ return 0;
4978
+ };
4979
+ if (!action || action === "list") {
4980
+ if (id) throw new UsageError(usage);
4981
+ const status = values.status ?? "open";
4982
+ if (!["open", "addressed", "closed", "all"].includes(status)) throw new UsageError("--status takes open, addressed, closed or all");
4983
+ const { requests, unreadable } = await store.list();
4984
+ const briefs = requests.filter((r) => (status === "all" || r.status === status) && (!values.route || r.target.route === values.route)).map((r) => requestBrief(r, root));
4985
+ if (values.json) json({ requests: briefs, unreadable });
4986
+ else print(renderRequests(briefs, status, unreadable));
4987
+ return unreadable.length ? 1 : 0;
4988
+ }
4989
+ if (!id) throw new UsageError(usage);
4990
+ if (action === "show") return one(await store.get(id));
4991
+ if (action === "reply" || action === "addressed") {
4992
+ if (!values.message?.trim()) throw new UsageError(`youiyoux requests ${action} ${id} --message <${action === "reply" ? "text" : "what you changed, with file:line"}>`);
4993
+ return one(action === "reply" ? await store.reply(agent, id, values.message) : await store.setStatus(agent, id, "addressed", values.message));
4994
+ }
4995
+ throw new UsageError(usage);
4996
+ }
4997
+ async function cmdScreens(values, positionals) {
4998
+ if (positionals[0] !== "capture" || positionals.length > 1 || !values.base) throw new UsageError("youiyoux screens capture --base <url of the app in development> [--route <one route>] [--project dir] [--out <screens dir>] [--storage-state <signed-in session>] [--json]");
4999
+ const project2 = await loadProject(resolve12(values.project ?? process.cwd()), loadOptions(values)).catch(() => null);
5000
+ const root = project2?.root ?? resolve12(values.root ?? values.project ?? process.cwd());
5001
+ const dir = resolve12(values.out ?? join22(root, ".youi/screens"));
5002
+ if (!existsSync4(join22(dir, "screens.json"))) {
5003
+ process.stderr.write(`${style.error("\u2716")} no screens.json in ${dir}: list the routes to capture there, as {"schema":"youiyoux.screens/v1","project":"<name>","source":"<what the app is>","capturedAt":"","viewport":{"width":1440,"height":900},"screens":[{"route":"/","path":"/"}]}
5004
+ `);
5005
+ return 1;
5006
+ }
5007
+ const { launchBrowser } = await import("./src-DS43LCRA.js");
5008
+ const browser = await launchBrowser(process.env.YOUI_CHROMIUM, { purpose: { what: "youiyoux screens capture", command: "youiyoux screens capture" } });
5009
+ try {
5010
+ const report = await captureScreens(dir, { base: values.base, root, browser, ...values.route ? { routes: [values.route] } : {}, ...values["storage-state"] ? { storageState: resolve12(values["storage-state"]) } : {}, log: (line) => process.stderr.write(`${style.dim(line)}
5011
+ `) });
5012
+ if (values.json) json(report);
5013
+ else print(captureText(report));
5014
+ return report.screens.every((s) => s.error) ? 1 : 0;
5015
+ } finally {
5016
+ await browser.close();
5017
+ }
5018
+ }
4906
5019
  async function cmdReport(values, positionals, io) {
4907
5020
  if (positionals.length > 1) throw new UsageError("youiyoux report [path] [--out dir] [--render [--yes]] [--include-code] [--json] (one repo)");
4908
5021
  const result = await runReport({ ...positionals[0] ? { path: positionals[0] } : {}, ...values.out ? { out: values.out } : {}, render: values.render, yes: values.yes, includeCode: values["include-code"], json: values.json }, io.report ?? {});
4909
5022
  return result.code;
4910
5023
  }
4911
5024
  async function cmdMcp(values) {
4912
- const { startStdioServer } = await import("./src-WQWYJ6IQ.js");
5025
+ const { startStdioServer } = await import("./src-YGEZCUAO.js");
4913
5026
  await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
4914
5027
  return -1;
4915
5028
  }
@@ -4941,6 +5054,11 @@ async function cmdDiscover(values, positionals, io) {
4941
5054
  ${style.dim(`store \u2192 ${storeLocation({ path })} (${plural(loaded.chosen, "listing")})`)}`);
4942
5055
  return 0;
4943
5056
  }
5057
+ if (action === "previews") {
5058
+ if (rest.length > 0) throw new UsageError("youiyoux discover previews [--port n] [--registry @shadcn|all] [--role r] [--limit n] [--force]");
5059
+ positiveInt(values.limit, "--limit");
5060
+ return runPreviews(TOOL_ROOT, { ...values.port ? { port: values.port } : {}, ...values.registry ? { registry: values.registry } : {}, ...values.role ? { role: values.role } : {}, ...values.limit ? { limit: values.limit } : {}, ...values.cache ? { cache: values.cache } : {}, force: values.force });
5061
+ }
4944
5062
  if (action === "show") {
4945
5063
  if (!rest[0]) throw new UsageError("youiyoux discover show <id|ref>");
4946
5064
  if (values.source) {
@@ -4987,6 +5105,8 @@ var COMMANDS = {
4987
5105
  index: cmdIndex,
4988
5106
  report: cmdReport,
4989
5107
  render: cmdRender,
5108
+ screens: cmdScreens,
5109
+ requests: cmdRequests,
4990
5110
  detect: cmdDetect,
4991
5111
  build: cmdBuild,
4992
5112
  parity: cmdParity,
@@ -6,7 +6,7 @@ import {
6
6
  external_exports,
7
7
  sha256,
8
8
  writeFileAtomic
9
- } from "./chunk-ZLRFD5JI.js";
9
+ } from "./chunk-C3TSTYP7.js";
10
10
 
11
11
  // ../discover/src/aliases.ts
12
12
  var CONCEPTS = [
@@ -112,7 +112,7 @@ function cleanData(value) {
112
112
  }
113
113
 
114
114
  // ../discover/src/sources.ts
115
- var CURATED = ["@coss", "@kibo-ui", "@magicui", "@origin", "@aceternity"];
115
+ var CURATED = ["@coss", "@kibo-ui", "@magicui", "@aceternity"];
116
116
  var SHADCN = {
117
117
  name: "@shadcn",
118
118
  homepage: "https://ui.shadcn.com",
@@ -1796,7 +1796,7 @@ function showCandidate(index, query) {
1796
1796
 
1797
1797
  // ../discover/src/signals.ts
1798
1798
  var HOUR = 36e5;
1799
- var SIGNAL_TTL = { npmMs: 24 * HOUR, githubMs: 7 * 24 * HOUR, registryRepoMs: 30 * 24 * HOUR };
1799
+ var SIGNAL_TTL = { npmMs: 24 * HOUR, githubMs: 7 * 24 * HOUR, registryRepoMs: 30 * 24 * HOUR, registryRepoMissMs: 24 * HOUR };
1800
1800
  function defaultBudgets(token) {
1801
1801
  return token ? { npmPackages: 400, githubRepos: 600, githubSearches: 30 } : { npmPackages: 200, githubRepos: 50, githubSearches: 10 };
1802
1802
  }
@@ -1823,6 +1823,34 @@ function sameHomepage(a, b) {
1823
1823
  const ka = key(a);
1824
1824
  return ka !== null && ka === key(b);
1825
1825
  }
1826
+ var PATH_TENANTS = /* @__PURE__ */ new Set(["github.com", "gitlab.com", "bitbucket.org", "codeberg.org", "npmjs.com", "codesandbox.io", "stackblitz.com", "gitbook.io", "medium.com", "notion.site"]);
1827
+ function sameSite(repository, registry) {
1828
+ const parts = (value) => {
1829
+ try {
1830
+ const url = new URL(value.includes("://") ? value : `https://${value}`);
1831
+ return { host: url.hostname.replace(/^www\./, "").toLowerCase(), path: url.pathname.replace(/\/+$/, "").toLowerCase() };
1832
+ } catch {
1833
+ return null;
1834
+ }
1835
+ };
1836
+ if (!repository || !registry) return false;
1837
+ const repo = parts(repository);
1838
+ const home = parts(registry);
1839
+ if (!repo || !home || repo.host !== home.host) return false;
1840
+ if (PATH_TENANTS.has(repo.host)) return repo.path === home.path && repo.path !== "";
1841
+ return repo.path === "" || home.path === repo.path || home.path.startsWith(`${repo.path}/`);
1842
+ }
1843
+ var NOT_OWNERS = /* @__PURE__ */ new Set(["about", "apps", "collections", "contact", "enterprise", "explore", "features", "login", "marketplace", "orgs", "pricing", "settings", "site", "sponsors", "topics"]);
1844
+ function linkedRepos(html) {
1845
+ const names = [];
1846
+ for (const match of html.matchAll(/https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9_.-]+)/g)) {
1847
+ const owner = match[1];
1848
+ const repo = match[2].replace(/\.git$/, "").replace(/\.+$/, "");
1849
+ if (NOT_OWNERS.has(owner.toLowerCase()) || !repo || repo.startsWith(".")) continue;
1850
+ names.push(`${owner}/${repo}`);
1851
+ }
1852
+ return byUse(names);
1853
+ }
1826
1854
  var GithubRepoBody = external_exports.object({
1827
1855
  full_name: external_exports.string(),
1828
1856
  html_url: external_exports.string(),
@@ -1886,9 +1914,19 @@ async function refreshSignals(entries, registries, previous, client, now, budget
1886
1914
  npm[name] = { weeklyDownloads: typeof count2 === "number" ? count2 : null, license: spdxOrNull(meta?.license), repo: githubRepo(typeof repository === "string" ? repository : null), fetchedAt: at };
1887
1915
  });
1888
1916
  registryRepos[SHADCN.name] = { repo: SHADCN.repo, checkedAt: at };
1889
- const unsearched = [...registries].filter((r) => r.name !== SHADCN.name && r.homepage && !fresh(registryRepos[r.name]?.checkedAt, SIGNAL_TTL.registryRepoMs, now)).sort((a, b) => b.items - a.items || a.name.localeCompare(b.name));
1890
- for (const registry of unsearched.slice(0, budgets.githubSearches)) {
1891
- if (!client.hasBudget("github-search")) break;
1917
+ const known = (name) => {
1918
+ const prior = registryRepos[name];
1919
+ return fresh(prior?.checkedAt, prior?.repo ? SIGNAL_TTL.registryRepoMs : SIGNAL_TTL.registryRepoMissMs, now);
1920
+ };
1921
+ const unsearched = [...registries].filter((r) => r.name !== SHADCN.name && r.homepage && !known(r.name)).sort((a, b) => b.items - a.items || a.name.localeCompare(b.name));
1922
+ const spend = { repoCalls: budgets.githubRepos };
1923
+ for (const registry of unsearched) {
1924
+ const linked = await homepageRepo(registry.homepage, client, github, at, report, spend);
1925
+ if (linked) {
1926
+ registryRepos[registry.name] = { repo: linked, checkedAt: at };
1927
+ continue;
1928
+ }
1929
+ if (report.github.searches >= budgets.githubSearches || !client.hasBudget("github-search")) continue;
1892
1930
  const query = `${sourceSlug(registry.name).replace(/-ui$/, "")} in:name`;
1893
1931
  const result = await client.json(`https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&per_page=10`);
1894
1932
  report.github.searches++;
@@ -1915,7 +1953,7 @@ async function refreshSignals(entries, registries, previous, client, now, budget
1915
1953
  }
1916
1954
  return true;
1917
1955
  });
1918
- const repoTodo = repoStale.slice(0, budgets.githubRepos);
1956
+ const repoTodo = repoStale.slice(0, spend.repoCalls);
1919
1957
  await pool(repoTodo, async (repo) => {
1920
1958
  if (!client.hasBudget("github-core")) return;
1921
1959
  const result = await client.json(`https://api.github.com/repos/${repo}`);
@@ -1931,12 +1969,35 @@ async function refreshSignals(entries, registries, previous, client, now, budget
1931
1969
  report.github.failed++;
1932
1970
  return;
1933
1971
  }
1934
- const body = parsed.data;
1935
- github[repo] = { found: true, repo: body.full_name, htmlUrl: body.html_url, stars: body.stargazers_count, pushedAt: body.pushed_at, archived: body.archived, spdx: spdxOrNull(body.license?.spdx_id), fetchedAt: at };
1972
+ github[repo] = githubSignal(parsed.data, at);
1936
1973
  });
1937
1974
  report.github.leftUnknown = repos.filter((repo) => !github[repo]).length;
1938
1975
  return { signals: { npm, github, registryRepos }, report };
1939
1976
  }
1977
+ function githubSignal(body, at) {
1978
+ return { found: true, repo: body.full_name, htmlUrl: body.html_url, stars: body.stargazers_count, pushedAt: body.pushed_at, archived: body.archived, spdx: spdxOrNull(body.license?.spdx_id), fetchedAt: at };
1979
+ }
1980
+ var HOMEPAGE_REPOS = 3;
1981
+ async function homepageRepo(homepage, client, github, at, report, spend) {
1982
+ const page = await client.get(homepage, "text/html");
1983
+ client.cache.delete(homepage);
1984
+ if (!page.ok) return null;
1985
+ for (const repo of linkedRepos(page.body).slice(0, HOMEPAGE_REPOS)) {
1986
+ if (spend.repoCalls <= 0 || !client.hasBudget("github-core")) return null;
1987
+ spend.repoCalls--;
1988
+ const result = await client.json(`https://api.github.com/repos/${repo}`);
1989
+ report.github.looked++;
1990
+ if (!result.ok) {
1991
+ if (result.reason === "rate-limited") report.github.rateLimited = true;
1992
+ continue;
1993
+ }
1994
+ const parsed = GithubRepoBody.safeParse(result.value);
1995
+ if (!parsed.success || !sameSite(parsed.data.homepage, homepage)) continue;
1996
+ github[parsed.data.full_name] = githubSignal(parsed.data, at);
1997
+ return parsed.data.full_name;
1998
+ }
1999
+ return null;
2000
+ }
1940
2001
  function repoOf(entry, signals) {
1941
2002
  const registry = registryOf(entry);
1942
2003
  if (registry) return signals.registryRepos[registry]?.repo ?? null;
@@ -2086,6 +2147,8 @@ async function syncIndex(options) {
2086
2147
  else if (options.maxRegistries !== void 0 && toFetch.length >= options.maxRegistries) sources.push({ id: registry.name, url: registryUrl(registry.template), outcome: "skipped", reason: "over --max-registries", httpStatus: null, items: 0, health: healthOf(registry) });
2087
2148
  else toFetch.push(registry);
2088
2149
  }
2150
+ const missing = directory2.ok ? selected.filter((name) => !listed.some((r) => r.name === name)) : [];
2151
+ for (const name of missing) sources.push({ id: name, url: SHADCN.directory, outcome: "skipped", reason: "not in the registry directory", httpStatus: null, items: 0, health: null });
2089
2152
  const community = await Promise.all(
2090
2153
  toFetch.map(async (registry) => {
2091
2154
  const url = registryUrl(registry.template);
@@ -2147,7 +2210,7 @@ async function syncIndex(options) {
2147
2210
  catalogBytes: await catalogBytes(options.cacheDir),
2148
2211
  selectedSources: selected.length,
2149
2212
  index: { bytes: Buffer.byteLength(JSON.stringify(index) + "\n"), entries: final.length, mirrorsCollapsed, iconRegistriesSkipped: sources.filter((s) => s.reason === iconReason).length },
2150
- warnings: client.warnings,
2213
+ warnings: [...client.warnings, ...missing.map((name) => `${name} is chosen but the registry directory no longer lists it: \`youiyoux discover sources remove ${name}\``)],
2151
2214
  builtAt,
2152
2215
  durationMs: Math.round(performance.now() - started),
2153
2216
  entries: { total: final.length, official: final.filter((e) => e.source.ref?.startsWith(`${SHADCN.name}/`)).length, community: final.filter((e) => e.source.kind === "shadcn-registry" && !e.source.ref?.startsWith(`${SHADCN.name}/`)).length, npm: final.filter((e) => e.source.kind === "npm").length },
@@ -2706,6 +2769,8 @@ export {
2706
2769
  keyPackage,
2707
2770
  registryOf,
2708
2771
  sameHomepage,
2772
+ sameSite,
2773
+ linkedRepos,
2709
2774
  refreshSignals,
2710
2775
  applySignals,
2711
2776
  dedupe,
@@ -36,8 +36,8 @@ import {
36
36
  siteReport,
37
37
  siteSignals,
38
38
  writeReport
39
- } from "./chunk-FUB7B3VK.js";
40
- import "./chunk-ZLRFD5JI.js";
39
+ } from "./chunk-74KBRI2H.js";
40
+ import "./chunk-C3TSTYP7.js";
41
41
  import "./chunk-E4HNK7VF.js";
42
42
  export {
43
43
  BrowserMissingError,
@@ -0,0 +1,148 @@
1
+ import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
2
+ import {
3
+ DEFAULT_FILES,
4
+ DRAFT_ID,
5
+ DesignFiles,
6
+ FileStore,
7
+ GitError,
8
+ JsonEditError,
9
+ MAX_FINDINGS,
10
+ MAX_LISTED,
11
+ MAX_TITLE,
12
+ MemoryStore,
13
+ REQUESTS_DIR,
14
+ RequestStore,
15
+ RuleWorkflow,
16
+ SESSION_HEADER,
17
+ WORKFLOW_VERSION,
18
+ WorkflowError,
19
+ adoptionRefusal,
20
+ authorize,
21
+ branchTip,
22
+ changesDigest,
23
+ comparePreview,
24
+ componentChanges,
25
+ contentDigest,
26
+ createHandler,
27
+ createReviewBranch,
28
+ defaultBranch,
29
+ deleteJsonPath,
30
+ describeContrast,
31
+ describeLibrary,
32
+ describeStyle,
33
+ draftStatus,
34
+ effectiveStateEdits,
35
+ effectiveTokenEdits,
36
+ emptyStyleContent,
37
+ fieldsFromRule,
38
+ git,
39
+ hasChanges,
40
+ headCommit,
41
+ isAncestor,
42
+ libraryDigest,
43
+ libraryPath,
44
+ librarySlug,
45
+ listAt,
46
+ listTreeAt,
47
+ locate,
48
+ newContrastFailures,
49
+ newRuleFields,
50
+ normalizeLibraryContent,
51
+ normalizeStyleContent,
52
+ previewStyle,
53
+ previewSummary,
54
+ readAt,
55
+ readJsonPath,
56
+ renderContract,
57
+ renderContractStates,
58
+ renderLibraryEntry,
59
+ renderRuleYaml,
60
+ renderTokenEdits,
61
+ requestBrief,
62
+ scannerExecution,
63
+ serveWorkflow,
64
+ setJsonPath,
65
+ styleDigest,
66
+ styleSlug,
67
+ toRuleSpec,
68
+ tokenPathOf,
69
+ validateContent,
70
+ validateLibraryContent,
71
+ validateStyleContent
72
+ } from "./chunk-OIHNEGWH.js";
73
+ import "./chunk-L5BV4PI7.js";
74
+ import "./chunk-S7UV34H5.js";
75
+ import "./chunk-C3TSTYP7.js";
76
+ import "./chunk-E4HNK7VF.js";
77
+ export {
78
+ DEFAULT_FILES,
79
+ DRAFT_ID,
80
+ DesignFiles,
81
+ RuleWorkflow as DesignWorkflow,
82
+ FileStore,
83
+ GitError,
84
+ JsonEditError,
85
+ MAX_FINDINGS,
86
+ MAX_LISTED,
87
+ MAX_TITLE,
88
+ MemoryStore,
89
+ REQUESTS_DIR,
90
+ RequestStore,
91
+ RuleWorkflow,
92
+ SESSION_HEADER,
93
+ WORKFLOW_VERSION,
94
+ WorkflowError,
95
+ adoptionRefusal,
96
+ authorize,
97
+ branchTip,
98
+ changesDigest,
99
+ comparePreview,
100
+ componentChanges,
101
+ contentDigest,
102
+ createHandler,
103
+ createReviewBranch,
104
+ defaultBranch,
105
+ deleteJsonPath,
106
+ describeContrast,
107
+ describeLibrary,
108
+ describeStyle,
109
+ draftStatus,
110
+ effectiveStateEdits,
111
+ effectiveTokenEdits,
112
+ emptyStyleContent,
113
+ fieldsFromRule,
114
+ git,
115
+ hasChanges,
116
+ headCommit,
117
+ isAncestor,
118
+ libraryDigest,
119
+ libraryPath,
120
+ librarySlug,
121
+ listAt,
122
+ listTreeAt,
123
+ locate,
124
+ newContrastFailures,
125
+ newRuleFields,
126
+ normalizeLibraryContent,
127
+ normalizeStyleContent,
128
+ previewStyle,
129
+ previewSummary,
130
+ readAt,
131
+ readJsonPath,
132
+ renderContract,
133
+ renderContractStates,
134
+ renderLibraryEntry,
135
+ renderRuleYaml,
136
+ renderTokenEdits,
137
+ requestBrief,
138
+ scannerExecution,
139
+ serveWorkflow,
140
+ setJsonPath,
141
+ styleDigest,
142
+ styleSlug,
143
+ toRuleSpec,
144
+ tokenPathOf,
145
+ validateContent,
146
+ validateLibraryContent,
147
+ validateStyleContent
148
+ };