youiyoux 0.1.15 → 0.1.16

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.
@@ -4,7 +4,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
4
4
  var PACKAGES = {
5
5
  core: () => import("./src-4BOFDAZD.js"),
6
6
  scanner: () => import("./src-6JO73FOS.js"),
7
- discover: () => import("./src-IMWXYWQK.js"),
7
+ discover: () => import("./src-FBTLHBVB.js"),
8
8
  tokens: () => import("./src-WMGLZKYK.js")
9
9
  };
10
10
  var loadPackage = (name) => PACKAGES[name]();
@@ -57,7 +57,7 @@ import { homedir, userInfo } from "node:os";
57
57
  import { join, resolve } from "node:path";
58
58
  async function swapImpact(candidate, family) {
59
59
  try {
60
- const { discoverIndexPath, sharedStore } = await import("./src-IMWXYWQK.js");
60
+ const { discoverIndexPath, sharedStore } = await import("./src-FBTLHBVB.js");
61
61
  const path = discoverIndexPath();
62
62
  if (!process.env.DATABASE_URL && !existsSync(path)) return null;
63
63
  const impact = await (await sharedStore(path, process.env, { readOnly: true })).swapImpact(candidate, family);
@@ -68,7 +68,7 @@ async function swapImpact(candidate, family) {
68
68
  }
69
69
  async function libraryLicence(id) {
70
70
  try {
71
- const { discoverCacheDir, findListing } = await import("./src-IMWXYWQK.js");
71
+ const { discoverCacheDir, findListing } = await import("./src-FBTLHBVB.js");
72
72
  const { entry } = await findListing(discoverCacheDir(void 0, process.env), id);
73
73
  return entry.id === id ? entry.license : null;
74
74
  } catch {
@@ -79,7 +79,7 @@ function workflowStateDir(project, state) {
79
79
  return resolve(state ?? join(homedir(), ".youi", "workflow", shortHash(`${project.repo.root}\0${project.root}`)));
80
80
  }
81
81
  async function ingest(link) {
82
- const { readLink } = await import("./ingest-R2TM3QBM.js");
82
+ const { readLink } = await import("./ingest-R5CX6R7G.js");
83
83
  return readLink(link, { allowLocal: false });
84
84
  }
85
85
  async function startRuleEditor(options) {
@@ -4,7 +4,7 @@ import {
4
4
  loadPackage,
5
5
  readTryIndex,
6
6
  send
7
- } from "./chunk-CZJFEND6.js";
7
+ } from "./chunk-3SBSYNAG.js";
8
8
  import {
9
9
  writeFileAtomic
10
10
  } from "./chunk-S7UN5KQK.js";
@@ -9,7 +9,7 @@ import {
9
9
  startRuleEditor,
10
10
  style,
11
11
  truncate
12
- } from "./chunk-QEUZDGIF.js";
12
+ } from "./chunk-ED35SP3F.js";
13
13
  import {
14
14
  BASELINE_FILE,
15
15
  DEFAULT_EXCLUDE,
@@ -96,7 +96,7 @@ import {
96
96
  sharedStore,
97
97
  showFromCatalog,
98
98
  syncIndex
99
- } from "./chunk-DAGYR4NV.js";
99
+ } from "./chunk-XSIH7RR4.js";
100
100
  import {
101
101
  checkpointSqlite,
102
102
  indexHome,
@@ -182,7 +182,7 @@ import { join as join2, relative, resolve } from "node:path";
182
182
 
183
183
  // ../cli/src/command.ts
184
184
  var COMMAND = "youiyoux";
185
- var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "upgrade", "record", "hub", "init", "adopt", "status", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
185
+ var SUBCOMMANDS = ["show", "where", "bom", "check", "baseline", "rules", "plan", "migrate", "upgrade", "record", "hub", "figma", "init", "adopt", "status", "system", "sync", "link", "unlink", "scan", "watch", "hooks", "agents", "mcp", "ui", "library", "discover", "index", "report", "render", "screens", "requests", "detect", "build", "parity", "schema", "inventory", "gaps", "help"];
186
186
 
187
187
  // ../cli/src/agents.ts
188
188
  var import_yaml = __toESM(require_dist(), 1);
@@ -5598,8 +5598,13 @@ ${style.bold("System")} ${style.dim("approved elements: build only with these")
5598
5598
  ${style.bold("Library")} ${style.dim("ingested references: browse and request adoption, never build with")}
5599
5599
  library search <query> Library items (lib: ids) and the System elements each could swap in for
5600
5600
  library request <id> --reason <text> draft an adoption request for a person to approve
5601
+ figma login [--paste] [--port n] | logout | status
5602
+ sign in to Figma once, for Figma links: through the browser with your team's
5603
+ private Figma app (FIGMA_CLIENT_ID, FIGMA_CLIENT_SECRET), else with a personal
5604
+ access token you paste; kept in the macOS Keychain (else a file only you can
5605
+ read). FIGMA_TOKEN still wins
5601
5606
  library ingest <link> [--draft] [--json]
5602
- read what a GitHub repository, a Storybook, a Figma file (FIGMA_TOKEN) or a
5607
+ read what a GitHub repository, a Storybook, a Figma file (youiyoux figma login) or a
5603
5608
  DESIGN.md holds into Library items, each sorted into a role, and show them;
5604
5609
  --draft files them as one draft in the changes queue, where a person keeps or
5605
5610
  drops each and approves the rest (youiyoux ui > Changes). GITHUB_TOKEN raises
@@ -5720,6 +5725,7 @@ var OPTIONS = {
5720
5725
  db: { type: "string" },
5721
5726
  record: { type: "boolean", default: false },
5722
5727
  draft: { type: "boolean", default: false },
5728
+ paste: { type: "boolean", default: false },
5723
5729
  cache: { type: "string" },
5724
5730
  role: { type: "string" },
5725
5731
  license: { type: "string" },
@@ -6001,7 +6007,7 @@ async function cmdLibrary(values, positionals) {
6001
6007
  return 0;
6002
6008
  }
6003
6009
  if (action === "ingest" && query && positionals.length === 2) {
6004
- const { fileIngestDraft, readLink, renderIngest } = await import("./ingest-R2TM3QBM.js");
6010
+ const { fileIngestDraft, readLink, renderIngest } = await import("./ingest-R5CX6R7G.js");
6005
6011
  const p = values.draft ? await project(values) : null;
6006
6012
  const reading = await readLink(query, { allowLocal: true });
6007
6013
  const filed = p ? await fileIngestDraft(p, reading, values.state) : null;
@@ -6040,6 +6046,48 @@ async function cmdRecord(values) {
6040
6046
  else print([renderRecord(record, sentTo), ...values.out ? [style.dim(` written to ${values.out}`)] : [], ...!values.send && !values.out ? [style.dim(` youiyoux record --send posts it to the hub in ${HUB_URL_ENV}; --out <file> writes it; --json prints it`)] : []].join("\n"));
6041
6047
  return 0;
6042
6048
  }
6049
+ async function cmdFigma(values, positionals) {
6050
+ const [action, ...extra] = positionals;
6051
+ const auth = await import("./figma-auth-ODUC3TSR.js");
6052
+ const store = auth.credentialStore();
6053
+ if (extra.length > 0 || !["login", "logout", "status"].includes(action ?? "")) throw new UsageError("youiyoux figma login [--paste] [--port n] or youiyoux figma logout or youiyoux figma status");
6054
+ if (action === "status") {
6055
+ const stored = await store.get();
6056
+ const lines = process.env.FIGMA_TOKEN ? [`${style.ok("FIGMA_TOKEN")} is set: it is used, before any sign-in`] : [];
6057
+ if (!stored) lines.push(process.env.FIGMA_TOKEN ? style.dim("no sign-in kept") : `${style.warn("not signed in")} to Figma: youiyoux figma login`);
6058
+ else if (stored.kind === "token") lines.push(`${style.ok("signed in")}${stored.handle ? ` as ${stored.handle}` : ""} with a personal access token, kept in ${store.where} since ${stored.savedAt.slice(0, 10)}`);
6059
+ else lines.push(`${style.ok("signed in")}${stored.handle ? ` as ${stored.handle}` : ""} through your team's Figma app, kept in ${store.where}; renewed before ${stored.expiresAt.slice(0, 10)}${auth.oauthAppOf(process.env) ? "" : style.warn(" (renewing needs FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET)")}`);
6060
+ print(lines.join("\n"));
6061
+ return stored || process.env.FIGMA_TOKEN ? 0 : 1;
6062
+ }
6063
+ if (action === "logout") {
6064
+ const removed = await store.delete();
6065
+ print(removed ? `${style.ok("signed out")}: the Figma sign-in is removed from ${store.where}. To end it in Figma as well, remove the token or the app in Figma > Settings > Security` : style.dim("not signed in: nothing to remove"));
6066
+ return 0;
6067
+ }
6068
+ const app = values.paste ? null : auth.oauthAppOf(process.env);
6069
+ if (app) {
6070
+ const port = values.port ? Number(values.port) : auth.DEFAULT_CALLBACK_PORT;
6071
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new UsageError(`--port ${JSON.stringify(values.port)}: expected a port from 1024 to 65535`);
6072
+ const credential2 = await auth.browserLogin({ app, port, say: (line) => print(style.dim(line)) });
6073
+ await store.set(credential2);
6074
+ print(`${style.ok("signed in")} to Figma${credential2.handle ? ` as ${credential2.handle}` : ""} through your team's app; kept in ${store.where}, renewed by itself`);
6075
+ return 0;
6076
+ }
6077
+ print([
6078
+ "Sign in with a Figma personal access token:",
6079
+ " 1. Figma > Settings > Security > Personal access tokens > Generate new token (opening it now)",
6080
+ " 2. Give it an expiry, and these scopes, read-only: File content, Library content, and Current user (to check it)",
6081
+ " 3. Paste it here; it is not shown",
6082
+ style.dim(` (A team can sign in through the browser instead: register a private Figma OAuth app with the redirect URL ${auth.callbackUrl(auth.DEFAULT_CALLBACK_PORT)}, then set FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET.)`)
6083
+ ].join("\n"));
6084
+ if (process.stdin.isTTY) auth.openBrowser("https://www.figma.com/settings");
6085
+ const token = await auth.readSecret("Token: ");
6086
+ const { credential, checked } = await auth.tokenLogin(token);
6087
+ await store.set(credential);
6088
+ print(`${style.ok("signed in")} to Figma${credential.handle ? ` as ${credential.handle}` : ""}; kept in ${store.where}${checked ? "" : style.warn(" (not checked: give the token Current user: Read to let youiyoux check it)")}`);
6089
+ return 0;
6090
+ }
6043
6091
  function builtHub() {
6044
6092
  for (const dir of [join27(TOOL_ROOT, "hub"), join27(TOOL_ROOT, "apps/web/dist")]) if (existsSync4(join27(dir, "index.html"))) return dir;
6045
6093
  return null;
@@ -6517,7 +6565,7 @@ async function cmdReport(values, positionals, io) {
6517
6565
  return result.code;
6518
6566
  }
6519
6567
  async function cmdMcp(values) {
6520
- const { startStdioServer } = await import("./src-OHHZY3TA.js");
6568
+ const { startStdioServer } = await import("./src-LAUPNDCB.js");
6521
6569
  await startStdioServer({ projectRoot: values.project ?? process.cwd(), ...loadOptions(values), ...values.cache ? { discoverCache: values.cache } : {} });
6522
6570
  return -1;
6523
6571
  }
@@ -6595,6 +6643,7 @@ var COMMANDS = {
6595
6643
  upgrade: cmdUpgrade,
6596
6644
  record: cmdRecord,
6597
6645
  hub: cmdHubServe,
6646
+ figma: cmdFigma,
6598
6647
  init: cmdInit,
6599
6648
  sync: cmdSync,
6600
6649
  scan: cmdScan,
@@ -700,7 +700,10 @@ var PoliteClient = class {
700
700
  if (cached?.etag) headers["if-none-match"] = cached.etag;
701
701
  if (cached?.lastModified) headers["if-modified-since"] = cached.lastModified;
702
702
  if (this.options.token && parsed.hostname === "api.github.com") headers.authorization = `Bearer ${this.options.token}`;
703
- if (this.options.figmaToken && parsed.hostname === "api.figma.com") headers["x-figma-token"] = this.options.figmaToken;
703
+ if (this.options.figmaToken && parsed.hostname === "api.figma.com") {
704
+ if (this.options.figmaBearer) headers.authorization = `Bearer ${this.options.figmaToken}`;
705
+ else headers["x-figma-token"] = this.options.figmaToken;
706
+ }
704
707
  this.stats.sent++;
705
708
  this.inFlight++;
706
709
  this.stats.maxInFlight = Math.max(this.stats.maxInFlight, this.inFlight);
@@ -2354,7 +2357,7 @@ async function ingestLink(link, options) {
2354
2357
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
2355
2358
  const target = classifyLink(link, options);
2356
2359
  const fetchedAt = now().toISOString();
2357
- const result = await (target.kind === "github" ? readGithub(target, options.client, fetchedAt) : target.kind === "storybook" ? readStorybook(target, options.client, fetchedAt) : target.kind === "figma" ? options.figma ? readFigma(target, options.client, fetchedAt) : Promise.reject(new IngestError("a Figma file is read through Figma's API: set FIGMA_TOKEN to a personal access token that can read the file (Figma > Settings > Security > Personal access tokens)")) : readDesignMd(target, options.client, fetchedAt, (path) => readFile3(path, "utf8")));
2360
+ const result = await (target.kind === "github" ? readGithub(target, options.client, fetchedAt) : target.kind === "storybook" ? readStorybook(target, options.client, fetchedAt) : target.kind === "figma" ? options.figma ? readFigma(target, options.client, fetchedAt) : Promise.reject(new IngestError("a Figma file is read through Figma's API: sign in with youiyoux figma login (or set FIGMA_TOKEN)")) : readDesignMd(target, options.client, fetchedAt, (path) => readFile3(path, "utf8")));
2358
2361
  const over = result.entries.length - INGEST_CAP;
2359
2362
  return cleanData({
2360
2363
  ...result,
@@ -0,0 +1,212 @@
1
+ import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
2
+ import {
3
+ writeFileAtomic
4
+ } from "./chunk-S7UN5KQK.js";
5
+ import "./chunk-WNTS7ZO4.js";
6
+
7
+ // ../cli/src/figma-auth.ts
8
+ import { execFile, spawn } from "node:child_process";
9
+ import { createHash, randomBytes } from "node:crypto";
10
+ import { chmod, mkdir, readFile, rm } from "node:fs/promises";
11
+ import { createServer } from "node:http";
12
+ import { homedir } from "node:os";
13
+ import { dirname, join } from "node:path";
14
+ import { promisify } from "node:util";
15
+ var run = promisify(execFile);
16
+ var API = "https://api.figma.com/v1";
17
+ var FIGMA_SCOPES = ["file_content:read", "library_content:read", "current_user:read"];
18
+ var DEFAULT_CALLBACK_PORT = 47823;
19
+ var RENEW_MS = 24 * 60 * 60 * 1e3;
20
+ var KEYCHAIN = { account: "youiyoux", service: "youiyoux-figma" };
21
+ var parse = (text) => {
22
+ if (!text) return null;
23
+ try {
24
+ const value = JSON.parse(text);
25
+ return value && (value.kind === "token" || value.kind === "oauth") ? value : null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ };
30
+ function fileStore(path) {
31
+ return {
32
+ where: path,
33
+ get: async () => parse(await readFile(path, "utf8").catch(() => null)),
34
+ set: async (credential) => {
35
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
36
+ await writeFileAtomic(path, `${JSON.stringify(credential)}
37
+ `);
38
+ await chmod(path, 384);
39
+ },
40
+ delete: async () => rm(path).then(() => true, () => false)
41
+ };
42
+ }
43
+ function keychainStore() {
44
+ const { account, service } = KEYCHAIN;
45
+ return {
46
+ where: "the macOS Keychain (youiyoux-figma)",
47
+ get: async () => parse(await run("security", ["find-generic-password", "-a", account, "-s", service, "-w"]).then((r) => Buffer.from(r.stdout.trim(), "base64").toString("utf8"), () => null)),
48
+ set: (credential) => new Promise((done, fail) => {
49
+ const child = spawn("security", ["-i"], { stdio: ["pipe", "ignore", "pipe"] });
50
+ let err = "";
51
+ child.stderr.on("data", (c) => err += String(c));
52
+ child.on("error", fail);
53
+ child.on("exit", (code) => code === 0 && !err.trim() ? done() : fail(new Error(`the Keychain refused the credential${err.trim() ? `: ${err.trim()}` : ""}`)));
54
+ child.stdin.end(`add-generic-password -a ${account} -s ${service} -U -w ${Buffer.from(JSON.stringify(credential)).toString("base64")}
55
+ `);
56
+ }),
57
+ delete: async () => run("security", ["delete-generic-password", "-a", account, "-s", service]).then(() => true, () => false)
58
+ };
59
+ }
60
+ function credentialStore(env = process.env, platform = process.platform) {
61
+ if (env.YOUI_FIGMA_CREDENTIALS) return fileStore(env.YOUI_FIGMA_CREDENTIALS);
62
+ if (platform === "darwin") return keychainStore();
63
+ return fileStore(join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "youiyoux", "figma.json"));
64
+ }
65
+ var basic = (id, secret) => `Basic ${Buffer.from(`${id}:${secret}`).toString("base64")}`;
66
+ async function postForm(fetcher, url, auth, form) {
67
+ const response = await fetcher(url, { method: "POST", headers: { authorization: auth, "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(form).toString(), signal: AbortSignal.timeout(15e3) });
68
+ const body = await response.json().catch(() => ({}));
69
+ const said = [body.message, body.err, body.error].find((v) => typeof v === "string");
70
+ if (!response.ok) throw new Error(`Figma refused (${response.status}): ${said ?? response.statusText}`);
71
+ return body;
72
+ }
73
+ async function whoAmI(token, bearer, fetcher = fetch) {
74
+ const response = await fetcher(`${API}/me`, { headers: bearer ? { authorization: `Bearer ${token}` } : { "x-figma-token": token }, signal: AbortSignal.timeout(15e3) });
75
+ if (!response.ok) return { ok: false, handle: null, status: response.status };
76
+ const me = await response.json().catch(() => ({}));
77
+ return { ok: true, handle: typeof me.handle === "string" ? me.handle : null, status: response.status };
78
+ }
79
+ var oauthAppOf = (env) => env.FIGMA_CLIENT_ID && env.FIGMA_CLIENT_SECRET ? { clientId: env.FIGMA_CLIENT_ID, clientSecret: env.FIGMA_CLIENT_SECRET } : null;
80
+ var callbackUrl = (port) => `http://127.0.0.1:${port}/callback`;
81
+ function openBrowser(url) {
82
+ const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
83
+ spawn(command, args, { stdio: "ignore", detached: true }).on("error", () => {
84
+ }).unref();
85
+ }
86
+ var page = (title, text) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font:16px system-ui;margin:3rem;max-width:32rem"><h1 style="font-size:1.25rem">${title}</h1><p>${text}</p></body>`;
87
+ async function browserLogin(options) {
88
+ const fetcher = options.fetcher ?? fetch;
89
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
90
+ const port = options.port ?? DEFAULT_CALLBACK_PORT;
91
+ const redirect = callbackUrl(port);
92
+ const state = randomBytes(24).toString("base64url");
93
+ const verifier = randomBytes(48).toString("base64url");
94
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
95
+ const authorize = new URL("https://www.figma.com/oauth");
96
+ for (const [key, value] of Object.entries({ client_id: options.app.clientId, redirect_uri: redirect, scope: FIGMA_SCOPES.join(","), state, response_type: "code", code_challenge: challenge, code_challenge_method: "S256" })) authorize.searchParams.set(key, value);
97
+ const code = await new Promise((done, fail) => {
98
+ const server = createServer((req, res) => {
99
+ const url = new URL(req.url ?? "/", redirect);
100
+ if (url.pathname !== "/callback") {
101
+ res.writeHead(404).end();
102
+ return;
103
+ }
104
+ const finish = (status, title, text, outcome) => {
105
+ res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(page(title, text));
106
+ clearTimeout(timer);
107
+ server.close();
108
+ outcome();
109
+ };
110
+ if (url.searchParams.get("state") !== state) return finish(400, "Not this sign-in", "This answer is not for the sign-in youiyoux started. Run youiyoux figma login again.", () => fail(new Error("the answer came with another sign-in's state; nothing was kept")));
111
+ const error = url.searchParams.get("error");
112
+ if (error) return finish(400, "Not signed in", "Figma did not sign you in. You can close this tab.", () => fail(new Error(`Figma did not sign you in: ${error}`)));
113
+ const given = url.searchParams.get("code");
114
+ if (!given) return finish(400, "Not signed in", "Figma sent no code. Run youiyoux figma login again.", () => fail(new Error("Figma sent no code")));
115
+ finish(200, "Signed in to Figma", "youiyoux can read your Figma files now. You can close this tab.", () => done(given));
116
+ });
117
+ const timer = setTimeout(() => {
118
+ server.close();
119
+ fail(new Error("no answer from Figma within five minutes; run youiyoux figma login again"));
120
+ }, options.timeoutMs ?? 5 * 6e4);
121
+ server.once("error", (error) => {
122
+ clearTimeout(timer);
123
+ fail(new Error(error.code === "EADDRINUSE" ? `port ${port} is taken: pass --port <n>, and register ${callbackUrl(port).replace(String(port), "<n>")} in the Figma app` : error.message));
124
+ });
125
+ server.listen(port, "127.0.0.1", () => {
126
+ options.say?.(`Opening Figma to sign in. If no browser opens, visit:
127
+ ${authorize.href}`);
128
+ (options.open ?? openBrowser)(authorize.href);
129
+ });
130
+ });
131
+ const tokens = await postForm(fetcher, `${API}/oauth/token`, basic(options.app.clientId, options.app.clientSecret), { redirect_uri: redirect, code, grant_type: "authorization_code", code_verifier: verifier });
132
+ const accessToken = tokens.access_token;
133
+ const refreshToken = tokens.refresh_token;
134
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string") throw new Error("Figma's answer had no tokens");
135
+ const seconds = typeof tokens.expires_in === "number" ? tokens.expires_in : 90 * 86400;
136
+ const me = await whoAmI(accessToken, true, fetcher).catch(() => ({ ok: false, handle: null, status: 0 }));
137
+ return { kind: "oauth", accessToken, refreshToken, expiresAt: new Date(now().getTime() + seconds * 1e3).toISOString(), clientId: options.app.clientId, savedAt: now().toISOString(), handle: me.handle };
138
+ }
139
+ async function tokenLogin(token, options = {}) {
140
+ const clean = token.trim();
141
+ if (!/^[\w-]{20,200}$/.test(clean)) throw new Error("that does not look like a Figma personal access token (figd_\u2026)");
142
+ const me = await whoAmI(clean, false, options.fetcher);
143
+ if (!me.ok && me.status !== 403) throw new Error(`Figma does not accept that token (${me.status}): make a new one`);
144
+ return { credential: { kind: "token", token: clean, savedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(), handle: me.handle }, checked: me.ok };
145
+ }
146
+ async function figmaAccess(options = {}) {
147
+ const env = options.env ?? process.env;
148
+ if (env.FIGMA_TOKEN) return { token: env.FIGMA_TOKEN, bearer: false, from: "env" };
149
+ const store = options.store ?? credentialStore(env);
150
+ const stored = await store.get();
151
+ if (!stored) return null;
152
+ if (stored.kind === "token") return { token: stored.token, bearer: false, from: "token" };
153
+ const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
154
+ if (Date.parse(stored.expiresAt) - now.getTime() > RENEW_MS) return { token: stored.accessToken, bearer: true, from: "oauth" };
155
+ const app = oauthAppOf(env);
156
+ if (!app || app.clientId !== stored.clientId) {
157
+ if (Date.parse(stored.expiresAt) > now.getTime()) return { token: stored.accessToken, bearer: true, from: "oauth" };
158
+ throw new Error("your Figma sign-in has lapsed, and renewing it needs your team's Figma app (FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET): set them and run youiyoux figma login");
159
+ }
160
+ const renewed = await postForm(options.fetcher ?? fetch, `${API}/oauth/refresh`, basic(app.clientId, app.clientSecret), { refresh_token: stored.refreshToken });
161
+ if (typeof renewed.access_token !== "string") throw new Error("Figma did not renew the sign-in: run youiyoux figma login");
162
+ const seconds = typeof renewed.expires_in === "number" ? renewed.expires_in : 90 * 86400;
163
+ await store.set({ ...stored, accessToken: renewed.access_token, expiresAt: new Date(now.getTime() + seconds * 1e3).toISOString() });
164
+ return { token: renewed.access_token, bearer: true, from: "oauth" };
165
+ }
166
+ async function readSecret(prompt, input = process.stdin, output = process.stderr) {
167
+ if (!input.isTTY) {
168
+ let text = "";
169
+ for await (const chunk of input) {
170
+ text += String(chunk);
171
+ if (text.includes("\n")) break;
172
+ }
173
+ return text.split(/\r?\n/)[0] ?? "";
174
+ }
175
+ output.write(prompt);
176
+ input.setRawMode(true);
177
+ input.resume();
178
+ return new Promise((done, fail) => {
179
+ let value = "";
180
+ const onData = (chunk) => {
181
+ for (const char of chunk.toString("utf8")) {
182
+ if (char === "\r" || char === "\n") return finish(() => done(value));
183
+ if (char === "") return finish(() => fail(new Error("cancelled")));
184
+ if (char === "\x7F" || char === "\b") value = value.slice(0, -1);
185
+ else if (char >= " ") value += char;
186
+ }
187
+ };
188
+ const finish = (then) => {
189
+ input.off("data", onData);
190
+ input.setRawMode(false);
191
+ input.pause();
192
+ output.write("\n");
193
+ then();
194
+ };
195
+ input.on("data", onData);
196
+ });
197
+ }
198
+ export {
199
+ DEFAULT_CALLBACK_PORT,
200
+ FIGMA_SCOPES,
201
+ browserLogin,
202
+ callbackUrl,
203
+ credentialStore,
204
+ figmaAccess,
205
+ fileStore,
206
+ keychainStore,
207
+ oauthAppOf,
208
+ openBrowser,
209
+ readSecret,
210
+ tokenLogin,
211
+ whoAmI
212
+ };
@@ -4,7 +4,7 @@ import {
4
4
  plural,
5
5
  style,
6
6
  workflowStateDir
7
- } from "./chunk-QEUZDGIF.js";
7
+ } from "./chunk-ED35SP3F.js";
8
8
  import "./chunk-HII2I2PI.js";
9
9
  import "./chunk-42YJX3W5.js";
10
10
  import "./chunk-HIXGUALJ.js";
@@ -14,12 +14,12 @@ import "./chunk-WNTS7ZO4.js";
14
14
  // ../cli/src/ingest.ts
15
15
  import { userInfo } from "node:os";
16
16
  async function readLink(link, options) {
17
- const { githubBuckets, ingestLink, PoliteClient } = await import("./src-IMWXYWQK.js");
17
+ const { githubBuckets, ingestLink, PoliteClient } = await import("./src-FBTLHBVB.js");
18
18
  const env = options.env ?? process.env;
19
19
  const token = env.GITHUB_TOKEN || void 0;
20
- const figmaToken = env.FIGMA_TOKEN || void 0;
21
- const client = new PoliteClient({ buckets: githubBuckets(token), ...token ? { token } : {}, ...figmaToken ? { figmaToken } : {} });
22
- const result = await ingestLink(link, { client, allowLocal: options.allowLocal, figma: Boolean(figmaToken) });
20
+ const figma = /figma\.com\//i.test(link) ? await (await import("./figma-auth-ODUC3TSR.js")).figmaAccess({ env }) : null;
21
+ const client = new PoliteClient({ buckets: githubBuckets(token), ...token ? { token } : {}, ...figma ? { figmaToken: figma.token, figmaBearer: figma.bearer } : {} });
22
+ const result = await ingestLink(link, { client, allowLocal: options.allowLocal, figma: Boolean(figma) });
23
23
  return { link: result.link, source: result.source, license: result.license, fetchedAt: result.fetchedAt, entries: result.entries, skipped: result.skipped, notes: result.notes };
24
24
  }
25
25
  async function fileIngestDraft(project, reading, state) {
@@ -127,7 +127,7 @@ import {
127
127
  writeHttpCache,
128
128
  writeJson,
129
129
  writeSources
130
- } from "./chunk-DAGYR4NV.js";
130
+ } from "./chunk-XSIH7RR4.js";
131
131
  import "./chunk-A2IZOTJG.js";
132
132
  import "./chunk-S7UN5KQK.js";
133
133
  import "./chunk-WNTS7ZO4.js";
@@ -51,7 +51,7 @@ import {
51
51
  searchCatalog,
52
52
  sharedStore,
53
53
  showFromCatalog
54
- } from "./chunk-DAGYR4NV.js";
54
+ } from "./chunk-XSIH7RR4.js";
55
55
  import {
56
56
  storeLocation
57
57
  } from "./chunk-A2IZOTJG.js";
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  discoverCacheDir,
8
8
  loadIndex
9
- } from "./chunks/chunk-DAGYR4NV.js";
9
+ } from "./chunks/chunk-XSIH7RR4.js";
10
10
  import "./chunks/chunk-A2IZOTJG.js";
11
11
  import {
12
12
  THUMBS_PATH,
@@ -17,8 +17,8 @@ import {
17
17
  selectCandidates,
18
18
  thumbsDir,
19
19
  writeThumbIndex
20
- } from "./chunks/chunk-FTL53MU3.js";
21
- import "./chunks/chunk-CZJFEND6.js";
20
+ } from "./chunks/chunk-FO3WR47A.js";
21
+ import "./chunks/chunk-3SBSYNAG.js";
22
22
  import "./chunks/chunk-FVVL5JCY.js";
23
23
  import "./chunks/chunk-S7UN5KQK.js";
24
24
  import "./chunks/chunk-WNTS7ZO4.js";
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
3
3
  import {
4
4
  discoverThumbsMiddleware
5
- } from "./chunks/chunk-FTL53MU3.js";
5
+ } from "./chunks/chunk-FO3WR47A.js";
6
6
  import {
7
7
  isLocalHost,
8
8
  loadPackage,
@@ -10,7 +10,7 @@ import {
10
10
  readTryIndex,
11
11
  send,
12
12
  tryMiddleware
13
- } from "./chunks/chunk-CZJFEND6.js";
13
+ } from "./chunks/chunk-3SBSYNAG.js";
14
14
  import "./chunks/chunk-FVVL5JCY.js";
15
15
  import "./chunks/chunk-S7UN5KQK.js";
16
16
  import "./chunks/chunk-WNTS7ZO4.js";
@@ -3,7 +3,7 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
3
3
  import {
4
4
  loadPackage,
5
5
  tryProject
6
- } from "./chunks/chunk-CZJFEND6.js";
6
+ } from "./chunks/chunk-3SBSYNAG.js";
7
7
  import "./chunks/chunk-FVVL5JCY.js";
8
8
  import "./chunks/chunk-WNTS7ZO4.js";
9
9
 
package/dist/youi.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
3
3
  import {
4
4
  main
5
- } from "./chunks/chunk-T4EHV4XY.js";
6
- import "./chunks/chunk-QEUZDGIF.js";
5
+ } from "./chunks/chunk-WMIJNZIL.js";
6
+ import "./chunks/chunk-ED35SP3F.js";
7
7
  import "./chunks/chunk-HII2I2PI.js";
8
8
  import "./chunks/chunk-42YJX3W5.js";
9
9
  import "./chunks/chunk-ALQVHSF2.js";
10
- import "./chunks/chunk-DAGYR4NV.js";
10
+ import "./chunks/chunk-XSIH7RR4.js";
11
11
  import "./chunks/chunk-A2IZOTJG.js";
12
12
  import "./chunks/chunk-FVVL5JCY.js";
13
13
  import "./chunks/chunk-HIXGUALJ.js";
package/dist/youiyoux.js CHANGED
@@ -3,12 +3,12 @@ import { createRequire as __youiRequire } from "node:module"; import { dirname a
3
3
  import {
4
4
  COMMAND_NAMES,
5
5
  main
6
- } from "./chunks/chunk-T4EHV4XY.js";
7
- import "./chunks/chunk-QEUZDGIF.js";
6
+ } from "./chunks/chunk-WMIJNZIL.js";
7
+ import "./chunks/chunk-ED35SP3F.js";
8
8
  import "./chunks/chunk-HII2I2PI.js";
9
9
  import "./chunks/chunk-42YJX3W5.js";
10
10
  import "./chunks/chunk-ALQVHSF2.js";
11
- import "./chunks/chunk-DAGYR4NV.js";
11
+ import "./chunks/chunk-XSIH7RR4.js";
12
12
  import "./chunks/chunk-A2IZOTJG.js";
13
13
  import "./chunks/chunk-FVVL5JCY.js";
14
14
  import "./chunks/chunk-HIXGUALJ.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "youiyoux",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Your agent rebuilds components you already have. youiyoux catches it: npx youiyoux turns any repo into a shareable report of near-duplicate components, hardcoded token values and off-library imports, with the fixes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,9 +46,9 @@
46
46
  "typescript": "6.0.3"
47
47
  },
48
48
  "devDependencies": {
49
- "@youiyoux/cli": "0.1.15",
50
- "@youiyoux/core": "0.1.15",
51
- "@youiyoux/scanner": "0.1.15"
49
+ "@youiyoux/cli": "0.1.16",
50
+ "@youiyoux/scanner": "0.1.16",
51
+ "@youiyoux/core": "0.1.16"
52
52
  },
53
53
  "exports": {
54
54
  "./package.json": "./package.json"