digdesign 0.6.2 → 0.7.0

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.
Files changed (2) hide show
  1. package/dist/index.js +218 -141
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2054,9 +2054,178 @@ async function impactCommand(name, options) {
2054
2054
  }
2055
2055
  }
2056
2056
 
2057
+ // src/commands/review.ts
2058
+ import { readFile as readFile8 } from "fs/promises";
2059
+ import path11 from "path";
2060
+
2061
+ // src/lib/version.ts
2062
+ var CLI_VERSION = "0.7.0";
2063
+
2064
+ // src/commands/mcp.ts
2065
+ import { createInterface as createInterface2 } from "readline";
2066
+ var DEFAULT_MCP_URL = "https://docs.digdesign.ir/api/mcp";
2067
+ function parseSse(body) {
2068
+ const messages = [];
2069
+ for (const event of body.split(/\r?\n\r?\n/)) {
2070
+ const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
2071
+ if (!data) continue;
2072
+ try {
2073
+ messages.push(JSON.parse(data));
2074
+ } catch {
2075
+ }
2076
+ }
2077
+ return messages;
2078
+ }
2079
+ function requestIds(message) {
2080
+ const list = Array.isArray(message) ? message : [message];
2081
+ return list.filter((item) => typeof item === "object" && item !== null).filter((item) => item.method !== void 0 && item.id !== void 0 && item.id !== null).map((item) => item.id);
2082
+ }
2083
+ function createBridge(url, fetchImpl = fetch) {
2084
+ let sessionId;
2085
+ let protocolVersion;
2086
+ return {
2087
+ async handle(line) {
2088
+ let message;
2089
+ try {
2090
+ message = JSON.parse(line);
2091
+ } catch {
2092
+ return [{ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }];
2093
+ }
2094
+ const ids = requestIds(message);
2095
+ const fail = (reason) => ids.map((id) => ({ jsonrpc: "2.0", id, error: { code: -32603, message: `Dig MCP (${url}): ${reason}` } }));
2096
+ let response;
2097
+ try {
2098
+ response = await fetchImpl(url, {
2099
+ method: "POST",
2100
+ headers: {
2101
+ "content-type": "application/json",
2102
+ accept: "application/json, text/event-stream",
2103
+ "user-agent": `digdesign-cli/${CLI_VERSION}`,
2104
+ ...sessionId && { "mcp-session-id": sessionId },
2105
+ ...protocolVersion && { "mcp-protocol-version": protocolVersion }
2106
+ },
2107
+ body: line
2108
+ });
2109
+ } catch (error) {
2110
+ return fail(`network error: ${error.message}`);
2111
+ }
2112
+ sessionId = response.headers.get("mcp-session-id") ?? sessionId;
2113
+ if (response.status === 202) return [];
2114
+ const body = await response.text();
2115
+ if (!response.ok) return fail(`HTTP ${response.status} ${body.slice(0, 200)}`);
2116
+ let replies;
2117
+ if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) replies = parseSse(body);
2118
+ else {
2119
+ try {
2120
+ replies = body.trim() ? [JSON.parse(body)] : [];
2121
+ } catch {
2122
+ return fail(`invalid JSON response: ${body.slice(0, 200)}`);
2123
+ }
2124
+ }
2125
+ for (const reply of replies.flat()) {
2126
+ const result = reply?.result;
2127
+ if (result?.protocolVersion) protocolVersion = result.protocolVersion;
2128
+ }
2129
+ return replies;
2130
+ }
2131
+ };
2132
+ }
2133
+ async function mcpCommand(options) {
2134
+ const url = options.url ?? process.env.DIG_MCP_URL ?? DEFAULT_MCP_URL;
2135
+ const bridge = createBridge(url);
2136
+ const pending = /* @__PURE__ */ new Set();
2137
+ process.stderr.write(`dig mcp: stdio bridge to ${url}
2138
+ `);
2139
+ const input = createInterface2({ input: process.stdin, crlfDelay: Infinity });
2140
+ for await (const line of input) {
2141
+ if (!line.trim()) continue;
2142
+ const task = bridge.handle(line).then((replies) => {
2143
+ for (const reply of replies) process.stdout.write(`${JSON.stringify(reply)}
2144
+ `);
2145
+ });
2146
+ pending.add(task);
2147
+ void task.finally(() => pending.delete(task));
2148
+ }
2149
+ await Promise.all(pending);
2150
+ }
2151
+
2152
+ // src/commands/review.ts
2153
+ var MAX_FILE_CHARS = 2e5;
2154
+ var MAX_FILES_PER_CALL = 25;
2155
+ async function readReviewFiles(paths, cwd) {
2156
+ const files = [];
2157
+ for (const input of paths) {
2158
+ const absolute = path11.resolve(cwd, input);
2159
+ let code;
2160
+ try {
2161
+ code = await readFile8(absolute, "utf8");
2162
+ } catch {
2163
+ throw new DigError(`\u0641\u0627\u06CC\u0644 \u062E\u0648\u0627\u0646\u062F\u0647 \u0646\u0634\u062F: ${input}`);
2164
+ }
2165
+ if (code.length > MAX_FILE_CHARS) throw new DigError(`${input} \u0628\u0632\u0631\u06AF\u200C\u062A\u0631 \u0627\u0632 ${MAX_FILE_CHARS} \u0646\u0648\u06CC\u0633\u0647 \u0627\u0633\u062A\u061B \u0628\u0647 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u06A9\u0648\u0686\u06A9\u200C\u062A\u0631 \u0628\u0634\u06A9\u0646.`);
2166
+ files.push({ path: path11.relative(cwd, absolute) || input, code });
2167
+ }
2168
+ return files;
2169
+ }
2170
+ async function callReview(url, files, fetchImpl = fetch) {
2171
+ let response;
2172
+ try {
2173
+ response = await fetchImpl(url, {
2174
+ method: "POST",
2175
+ headers: {
2176
+ "content-type": "application/json",
2177
+ accept: "application/json, text/event-stream",
2178
+ "user-agent": `digdesign-cli/${CLI_VERSION}`
2179
+ },
2180
+ body: JSON.stringify({
2181
+ jsonrpc: "2.0",
2182
+ id: 1,
2183
+ method: "tools/call",
2184
+ params: { name: "review_code", arguments: { files } }
2185
+ })
2186
+ });
2187
+ } catch (error) {
2188
+ throw new DigError(`\u0627\u062A\u0635\u0627\u0644 \u0628\u0647 ${url} \u0628\u0631\u0642\u0631\u0627\u0631 \u0646\u0634\u062F: ${error.message}`);
2189
+ }
2190
+ const body = await response.text();
2191
+ if (!response.ok) throw new DigError(`\u0628\u0627\u0632\u0628\u06CC\u0646\u06CC \u0634\u06A9\u0633\u062A \u062E\u0648\u0631\u062F (HTTP ${response.status}).`);
2192
+ const message = (response.headers.get("content-type") ?? "").includes("text/event-stream") ? parseSse(body)[0] : JSON.parse(body);
2193
+ const text = message?.result?.content?.[0]?.text;
2194
+ if (!text) throw new DigError("\u067E\u0627\u0633\u062E\u0650 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0632 \u0633\u0631\u0648\u0631 MCP.");
2195
+ return JSON.parse(text);
2196
+ }
2197
+ async function reviewCommand(paths, options) {
2198
+ if (paths.length === 0) throw new DigError("\u062D\u062F\u0627\u0642\u0644 \u06CC\u06A9 \u0645\u0633\u06CC\u0631 \u0641\u0627\u06CC\u0644 \u0628\u062F\u0647: dig review src/app/page.tsx");
2199
+ const url = options.url ?? process.env.DIG_MCP_URL ?? DEFAULT_MCP_URL;
2200
+ const files = await readReviewFiles(paths, process.cwd());
2201
+ const merged = [];
2202
+ const reviewed = [];
2203
+ for (let i = 0; i < files.length; i += MAX_FILES_PER_CALL) {
2204
+ const result = await callReview(url, files.slice(i, i + MAX_FILES_PER_CALL));
2205
+ if (result.status === "invalid_input") throw new DigError(result.message ?? "\u0648\u0631\u0648\u062F\u06CC\u0650 \u0628\u0627\u0632\u0628\u06CC\u0646\u06CC \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u0627\u0633\u062A.");
2206
+ merged.push(...result.findings ?? []);
2207
+ reviewed.push(...result.reviewed ?? []);
2208
+ }
2209
+ const errors = merged.filter((finding) => finding.severity !== "warning").length;
2210
+ const status = errors > 0 ? "issues" : merged.length > 0 ? "warnings" : "ok";
2211
+ if (options.json) {
2212
+ console.log(JSON.stringify({ status, findings: merged, reviewed }, null, 2));
2213
+ } else {
2214
+ for (const finding of merged) {
2215
+ const where = `${finding.file ?? ""}:${finding.line}`;
2216
+ console.log(`${where} [${finding.severity ?? "error"}] ${finding.rule}
2217
+ ${finding.found}
2218
+ \u2190 ${finding.fix}`);
2219
+ }
2220
+ const lines = reviewed.reduce((sum, file) => sum + file.lines, 0);
2221
+ console.log(merged.length === 0 ? `\u062A\u062E\u0644\u0641\u06CC \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F (${files.length} \u0641\u0627\u06CC\u0644\u060C ${lines} \u062E\u0637).` : `${merged.length} \u06CC\u0627\u0641\u062A\u0647 \u062F\u0631 ${files.length} \u0641\u0627\u06CC\u0644 (${lines} \u062E\u0637).`);
2222
+ }
2223
+ if (errors > 0) process.exitCode = 1;
2224
+ }
2225
+
2057
2226
  // src/commands/skill.ts
2058
2227
  import { mkdir as mkdir4, writeFile as writeFile6 } from "fs/promises";
2059
- import path11 from "path";
2228
+ import path12 from "path";
2060
2229
  var SKILL_PATH = ".claude/skills/dig-design-system/SKILL.md";
2061
2230
  async function skillCommand() {
2062
2231
  const cwd = process.cwd();
@@ -2069,21 +2238,21 @@ async function skillCommand() {
2069
2238
  throw new DigError(`\u0627\u062A\u0635\u0627\u0644 \u0628\u0647 ${url.origin} \u0628\u0631\u0627\u06CC \u06AF\u0631\u0641\u062A\u0646\u0650 Skill \u0628\u0631\u0642\u0631\u0627\u0631 \u0646\u0634\u062F.`);
2070
2239
  }
2071
2240
  if (!response.ok) throw new DigError(`Skill \u06AF\u0631\u0641\u062A\u0647 \u0646\u0634\u062F: ${response.status}`);
2072
- const target = path11.join(cwd, SKILL_PATH);
2073
- await mkdir4(path11.dirname(target), { recursive: true });
2241
+ const target = path12.join(cwd, SKILL_PATH);
2242
+ await mkdir4(path12.dirname(target), { recursive: true });
2074
2243
  await writeFile6(target, await response.text(), "utf8");
2075
2244
  console.log(`\u2713 ${SKILL_PATH} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F (Skill\u0650 \u0631\u0633\u0645\u06CC\u0650 \u062F\u06CC\u06AF \u0628\u0631\u0627\u06CC Claude Code).`);
2076
2245
  }
2077
2246
 
2078
2247
  // src/commands/tokens.ts
2079
- import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
2080
- import path12 from "path";
2248
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
2249
+ import path13 from "path";
2081
2250
  async function tokensCommand(options) {
2082
2251
  const cwd = process.cwd();
2083
2252
  const registryUrl = await configExists(cwd) ? (await readConfig(cwd)).registryUrl : defaultConfig().registryUrl;
2084
- const designMd = await readFile8(path12.join(cwd, DESIGN_MD_FILE), "utf8").catch(() => "");
2253
+ const designMd = await readFile9(path13.join(cwd, DESIGN_MD_FILE), "utf8").catch(() => "");
2085
2254
  const brand = options.brand ?? readDesignMdSettings(designMd).brand;
2086
- const outDir = path12.join(cwd, options.out ?? "tokens");
2255
+ const outDir = path13.join(cwd, options.out ?? "tokens");
2087
2256
  await mkdir5(outDir, { recursive: true });
2088
2257
  for (const mode of ["light", "dark"]) {
2089
2258
  const url = new URL("/api/tokens", registryUrl);
@@ -2099,20 +2268,20 @@ async function tokensCommand(options) {
2099
2268
  const body = await response.json().catch(() => null);
2100
2269
  throw new DigError(`\u062A\u0648\u06A9\u0646\u200C\u0647\u0627 \u06AF\u0631\u0641\u062A\u0647 \u0646\u0634\u062F: ${body?.error ?? response.status}`);
2101
2270
  }
2102
- const file = path12.join(outDir, `dig-${mode}.tokens.json`);
2271
+ const file = path13.join(outDir, `dig-${mode}.tokens.json`);
2103
2272
  await writeFile7(file, await response.text(), "utf8");
2104
- console.log(`\u2713 ${path12.relative(cwd, file)} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F${brand ? ` (\u0628\u0631\u0646\u062F ${brand})` : ""}.`);
2273
+ console.log(`\u2713 ${path13.relative(cwd, file)} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F${brand ? ` (\u0628\u0631\u0646\u062F ${brand})` : ""}.`);
2105
2274
  }
2106
2275
  console.log("\n\u062F\u0631 \u0641\u06CC\u06AF\u0645\u0627 \u0647\u0631 \u0641\u0627\u06CC\u0644 \u0631\u0627 \u06CC\u06A9 mode \u0627\u0632 Variables \u0648\u0627\u0631\u062F \u06A9\u0646\u06CC\u062F (\u0631\u0648\u0634\u0646 \u0648 \u062A\u06CC\u0631\u0647).");
2107
2276
  }
2108
2277
 
2109
2278
  // src/commands/init.ts
2110
- import { readFile as readFile11, writeFile as writeFile10 } from "fs/promises";
2111
- import path16 from "path";
2279
+ import { readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
2280
+ import path17 from "path";
2112
2281
 
2113
2282
  // src/lib/agent-instructions.ts
2114
- import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
2115
- import path13 from "path";
2283
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
2284
+ import path14 from "path";
2116
2285
  var AGENT_CLIENTS = [
2117
2286
  { value: "claude-code", label: "Claude Code (CLAUDE.md)" },
2118
2287
  { value: "codex", label: "Codex (AGENTS.md)" },
@@ -2164,16 +2333,16 @@ async function writeAgentInstructions(cwd, registryUrl, options) {
2164
2333
  const files = await fetchSetupFiles(registryUrl, options);
2165
2334
  const instructions = files.find((item) => /^[A-Z]+\.md$/.test(item.path) && item.path !== "DESIGN.md");
2166
2335
  if (!instructions) throw new DigError("\u0641\u0627\u06CC\u0644\u0650 \u0627\u06CC\u062C\u0646\u062A \u062F\u0631 \u067E\u0627\u0633\u062E\u0650 \u0633\u0631\u0648\u0631 \u0646\u0628\u0648\u062F.");
2167
- const target = path13.join(cwd, instructions.path);
2168
- const existing = await readFile9(target, "utf8").catch(() => null);
2336
+ const target = path14.join(cwd, instructions.path);
2337
+ const existing = await readFile10(target, "utf8").catch(() => null);
2169
2338
  await writeFile8(target, mergeInstructions(existing, instructions.content), "utf8");
2170
2339
  console.log(
2171
2340
  existing === null ? `\u2713 ${instructions.path} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F (\u0627\u06CC\u062C\u0646\u062A \u0641\u0648\u0646\u062A \u0648 \u0631\u0646\u06AF \u0631\u0627 \u0627\u0632 \u0627\u06CC\u0646 \u0641\u0627\u06CC\u0644 \u0645\u06CC\u200C\u062E\u0648\u0627\u0646\u062F \u0648 \u062F\u0648\u0628\u0627\u0631\u0647 \u0646\u0645\u06CC\u200C\u067E\u0631\u0633\u062F).` : `\u2713 \u0628\u062E\u0634\u0650 \u062F\u06CC\u06AF \u062F\u0631 ${instructions.path} \u0628\u0647\u200C\u0631\u0648\u0632 \u0634\u062F\u061B \u0628\u0642\u06CC\u0647\u0654 \u0641\u0627\u06CC\u0644 \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F.`
2172
2341
  );
2173
2342
  const skill = files.find((item) => item.path.endsWith("/SKILL.md"));
2174
2343
  if (skill) {
2175
- const skillTarget = path13.join(cwd, skill.path);
2176
- await mkdir6(path13.dirname(skillTarget), { recursive: true });
2344
+ const skillTarget = path14.join(cwd, skill.path);
2345
+ await mkdir6(path14.dirname(skillTarget), { recursive: true });
2177
2346
  await writeFile8(skillTarget, skill.content, "utf8");
2178
2347
  console.log(`\u2713 ${skill.path} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F (Skill\u0650 \u0631\u0633\u0645\u06CC\u0650 \u062F\u06CC\u06AF \u0628\u0631\u0627\u06CC \u0633\u0627\u062E\u062A\u0646\u0650 \u0635\u0641\u062D\u0647).`);
2179
2348
  }
@@ -2181,8 +2350,8 @@ async function writeAgentInstructions(cwd, registryUrl, options) {
2181
2350
  }
2182
2351
 
2183
2352
  // src/commands/theme.ts
2184
- import { access as access5, mkdir as mkdir7, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
2185
- import path15 from "path";
2353
+ import { access as access5, mkdir as mkdir7, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
2354
+ import path16 from "path";
2186
2355
 
2187
2356
  // src/lib/brand-ramp.ts
2188
2357
  var RAMP_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
@@ -2286,9 +2455,9 @@ function buildBrandRamp(hex) {
2286
2455
  }
2287
2456
 
2288
2457
  // src/lib/theme-files.ts
2289
- import path14 from "path";
2458
+ import path15 from "path";
2290
2459
  function importPath(fromFile, toFile) {
2291
- const rel = toPosixPath(path14.relative(path14.dirname(fromFile), toFile));
2460
+ const rel = toPosixPath(path15.relative(path15.dirname(fromFile), toFile));
2292
2461
  return rel.startsWith(".") ? rel : `./${rel}`;
2293
2462
  }
2294
2463
  function globalsCss(paths, extraStatements = []) {
@@ -2453,7 +2622,7 @@ function splitTheme(projectCss, digThemeCss) {
2453
2622
  var THEME_ITEM = "theme";
2454
2623
  async function readIfExists2(target) {
2455
2624
  try {
2456
- return await readFile10(target, "utf8");
2625
+ return await readFile11(target, "utf8");
2457
2626
  } catch {
2458
2627
  return null;
2459
2628
  }
@@ -2467,7 +2636,7 @@ async function exists(target) {
2467
2636
  }
2468
2637
  }
2469
2638
  function themePaths(config) {
2470
- const dir = path15.posix.dirname(toPosixPath(config.tailwind.css));
2639
+ const dir = path16.posix.dirname(toPosixPath(config.tailwind.css));
2471
2640
  return {
2472
2641
  css: config.tailwind.css,
2473
2642
  theme: config.tailwind.theme ?? `${dir}/dig-theme.css`,
@@ -2501,8 +2670,8 @@ async function recordTheme(cwd, config, themeRel, item) {
2501
2670
  await writeLock(lock, cwd);
2502
2671
  }
2503
2672
  async function writeText(cwd, rel, content) {
2504
- const target = path15.join(cwd, rel);
2505
- await mkdir7(path15.dirname(target), { recursive: true });
2673
+ const target = path16.join(cwd, rel);
2674
+ await mkdir7(path16.dirname(target), { recursive: true });
2506
2675
  await writeFile9(target, content, "utf8");
2507
2676
  }
2508
2677
  async function installTheme(cwd, config, options) {
@@ -2511,7 +2680,7 @@ async function installTheme(cwd, config, options) {
2511
2680
  const ramp = options.brand ? buildBrandRamp(options.brand) : void 0;
2512
2681
  await writeText(cwd, paths.theme, item.content);
2513
2682
  console.log(`\u2713 \u062A\u0645\u0650 \u062F\u06CC\u06AF \u062F\u0631 ${paths.theme} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F (\u0645\u0627\u0644\u0650 \u062F\u06CC\u06AF\u061B \u0628\u0627 \xABdig update theme\xBB \u0628\u0647\u200C\u0631\u0648\u0632 \u0645\u06CC\u200C\u0634\u0648\u062F).`);
2514
- const existingBrand = await readIfExists2(path15.join(cwd, paths.brand));
2683
+ const existingBrand = await readIfExists2(path16.join(cwd, paths.brand));
2515
2684
  if (existingBrand === null) {
2516
2685
  await writeText(cwd, paths.brand, brandCss({ ramp, hex: options.brand }));
2517
2686
  console.log(`\u2713 ${paths.brand} \u0633\u0627\u062E\u062A\u0647 \u0634\u062F (\u0645\u0627\u0644\u0650 \u067E\u0631\u0648\u0698\u0647\u061B \u062F\u06CC\u06AF \u0647\u0631\u06AF\u0632 \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC\u200C\u0627\u0634 \u0646\u0645\u06CC\u200C\u06A9\u0646\u062F).`);
@@ -2522,7 +2691,7 @@ async function installTheme(cwd, config, options) {
2522
2691
  console.log(`- ${paths.brand} \u0627\u0632 \u0642\u0628\u0644 \u0647\u0633\u062A\u061B \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F.`);
2523
2692
  }
2524
2693
  if (ramp) reportContrast(ramp.primaryContrast);
2525
- const cssTarget = path15.join(cwd, paths.css);
2694
+ const cssTarget = path16.join(cwd, paths.css);
2526
2695
  const existingCss = await readIfExists2(cssTarget);
2527
2696
  if (!isBoilerplateCss(existingCss) && existingCss !== null) {
2528
2697
  await writeFile9(`${cssTarget}.backup`, existingCss, "utf8");
@@ -2558,11 +2727,11 @@ async function themeSplitCommand(options) {
2558
2727
  return;
2559
2728
  }
2560
2729
  const paths = themePaths(config);
2561
- const current = await readIfExists2(path15.join(cwd, paths.css));
2730
+ const current = await readIfExists2(path16.join(cwd, paths.css));
2562
2731
  if (current === null || isBoilerplateCss(current)) {
2563
2732
  throw new DigError(`${paths.css} \u062A\u0645\u06CC \u0646\u062F\u0627\u0631\u062F\u061B \u0628\u0631\u0627\u06CC \u0646\u0635\u0628\u0650 \u062A\u0627\u0632\u0647 \xABdig init\xBB \u0631\u0627 \u0627\u062C\u0631\u0627 \u06A9\u0646\u06CC\u062F.`);
2564
2733
  }
2565
- if (await exists(path15.join(cwd, paths.brand))) {
2734
+ if (await exists(path16.join(cwd, paths.brand))) {
2566
2735
  throw new DigError(`${paths.brand} \u0627\u0632 \u0642\u0628\u0644 \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F\u061B \u0628\u0631\u0627\u06CC \u062C\u0644\u0648\u06AF\u06CC\u0631\u06CC \u0627\u0632 \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC\u060C \u0627\u0648\u0644 \u062C\u0627\u0628\u0647\u200C\u062C\u0627\u06CC\u0634 \u06A9\u0646\u06CC\u062F.`);
2567
2736
  }
2568
2737
  console.log(`\u062F\u0631\u06CC\u0627\u0641\u062A\u0650 \u062A\u0645\u0650 \u062F\u06CC\u06AF \u0627\u0632 ${config.registryUrl} \u2026`);
@@ -2581,7 +2750,7 @@ ${split.changed} \u062A\u0648\u06A9\u0646\u0650 \u0639\u0648\u0636\u200C\u0634\u
2581
2750
  console.log(split.body || "(\u062E\u0627\u0644\u06CC: \u067E\u0631\u0648\u0698\u0647 \u0686\u06CC\u0632\u06CC \u062F\u0631 \u062A\u0645 \u0639\u0648\u0636 \u0646\u06A9\u0631\u062F\u0647)");
2582
2751
  return;
2583
2752
  }
2584
- await writeFile9(path15.join(cwd, `${paths.css}.backup`), current, "utf8");
2753
+ await writeFile9(path16.join(cwd, `${paths.css}.backup`), current, "utf8");
2585
2754
  await writeText(cwd, paths.theme, item.content);
2586
2755
  await writeText(cwd, paths.brand, brandCss({ body: split.body }));
2587
2756
  await writeText(cwd, paths.css, globalsCss(paths, split.extraStatements));
@@ -2605,7 +2774,7 @@ async function themeBrandCommand(hex) {
2605
2774
  }
2606
2775
  const paths = themePaths(config);
2607
2776
  const ramp = buildBrandRamp(hex);
2608
- const existing = await readIfExists2(path15.join(cwd, paths.brand)) ?? brandCss({});
2777
+ const existing = await readIfExists2(path16.join(cwd, paths.brand)) ?? brandCss({});
2609
2778
  const next = replaceBrandRamp(existing, ramp, hex);
2610
2779
  await writeText(cwd, paths.brand, next);
2611
2780
  console.log(`\u2713 \u0631\u0645\u067E\u0650 \u0628\u0631\u0646\u062F \u062F\u0631 ${paths.brand} \u0627\u0632 ${hex} \u0633\u0627\u062E\u062A\u0647 \u0634\u062F (Shade \u06F6\u06F0\u06F0 = \u0631\u0646\u06AF\u0650 \u062F\u0627\u062F\u0647\u200C\u0634\u062F\u0647).`);
@@ -2623,14 +2792,14 @@ async function themeBrandCommand(hex) {
2623
2792
  // src/commands/init.ts
2624
2793
  async function readIfExists3(target) {
2625
2794
  try {
2626
- return await readFile11(target, "utf8");
2795
+ return await readFile12(target, "utf8");
2627
2796
  } catch {
2628
2797
  return null;
2629
2798
  }
2630
2799
  }
2631
2800
  async function writeTheme(config, cwd, options) {
2632
2801
  const paths = themePaths(config);
2633
- const existing = await readIfExists3(path16.join(cwd, paths.css));
2802
+ const existing = await readIfExists3(path17.join(cwd, paths.css));
2634
2803
  if (!isBoilerplateCss(existing) && !options.yes) {
2635
2804
  console.log(`
2636
2805
  ${paths.css} \u0627\u0632 \u0642\u0628\u0644 \u0645\u062D\u062A\u0648\u0627 \u062F\u0627\u0631\u062F.`);
@@ -2655,7 +2824,7 @@ ${paths.css} \u0627\u0632 \u0642\u0628\u0644 \u0645\u062D\u062A\u0648\u0627 \u06
2655
2824
  }
2656
2825
  async function applyRtl(cwd) {
2657
2826
  for (const candidate of ["src/app/layout.tsx", "app/layout.tsx"]) {
2658
- const target = path16.join(cwd, candidate);
2827
+ const target = path17.join(cwd, candidate);
2659
2828
  const source = await readIfExists3(target);
2660
2829
  if (source === null) continue;
2661
2830
  if (/<html[^>]*\sdir=/.test(source)) {
@@ -2700,14 +2869,14 @@ async function initCommand(options) {
2700
2869
  rtl: await confirm("\u067E\u0631\u0648\u0698\u0647 \u0631\u0627\u0633\u062A\u200C\u0628\u0647\u200C\u0686\u067E (RTL) \u0627\u0633\u062A\u061F", base.rtl),
2701
2870
  tailwind: await (async () => {
2702
2871
  const css = await ask("\u0645\u0633\u06CC\u0631 \u0641\u0627\u06CC\u0644 CSS \u0627\u0635\u0644\u06CC \u062A\u06CC\u0644\u0648\u06CC\u0646\u062F\u061F", base.tailwind.css);
2703
- const dir = path16.posix.dirname(css.split(path16.sep).join("/"));
2872
+ const dir = path17.posix.dirname(css.split(path17.sep).join("/"));
2704
2873
  return { ...base.tailwind, css, theme: `${dir}/dig-theme.css`, brand: `${dir}/brand.css` };
2705
2874
  })()
2706
2875
  };
2707
2876
  const decisions = await askProjectDecisions(options);
2708
2877
  options = { ...options, brand: decisions.brand };
2709
2878
  await writeConfig(config, cwd);
2710
- console.log(`\u2713 ${path16.join(cwd, "dig.json")} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F.`);
2879
+ console.log(`\u2713 ${path17.join(cwd, "dig.json")} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F.`);
2711
2880
  const theme2 = await writeTheme(config, cwd, options);
2712
2881
  if (config.rtl) {
2713
2882
  await applyRtl(cwd);
@@ -2731,102 +2900,9 @@ async function initCommand(options) {
2731
2900
  }
2732
2901
  }
2733
2902
 
2734
- // src/commands/mcp.ts
2735
- import { createInterface as createInterface2 } from "readline";
2736
-
2737
- // src/lib/version.ts
2738
- var CLI_VERSION = "0.6.2";
2739
-
2740
- // src/commands/mcp.ts
2741
- var DEFAULT_MCP_URL = "https://docs.digdesign.ir/api/mcp";
2742
- function parseSse(body) {
2743
- const messages = [];
2744
- for (const event of body.split(/\r?\n\r?\n/)) {
2745
- const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
2746
- if (!data) continue;
2747
- try {
2748
- messages.push(JSON.parse(data));
2749
- } catch {
2750
- }
2751
- }
2752
- return messages;
2753
- }
2754
- function requestIds(message) {
2755
- const list = Array.isArray(message) ? message : [message];
2756
- return list.filter((item) => typeof item === "object" && item !== null).filter((item) => item.method !== void 0 && item.id !== void 0 && item.id !== null).map((item) => item.id);
2757
- }
2758
- function createBridge(url, fetchImpl = fetch) {
2759
- let sessionId;
2760
- let protocolVersion;
2761
- return {
2762
- async handle(line) {
2763
- let message;
2764
- try {
2765
- message = JSON.parse(line);
2766
- } catch {
2767
- return [{ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }];
2768
- }
2769
- const ids = requestIds(message);
2770
- const fail = (reason) => ids.map((id) => ({ jsonrpc: "2.0", id, error: { code: -32603, message: `Dig MCP (${url}): ${reason}` } }));
2771
- let response;
2772
- try {
2773
- response = await fetchImpl(url, {
2774
- method: "POST",
2775
- headers: {
2776
- "content-type": "application/json",
2777
- accept: "application/json, text/event-stream",
2778
- "user-agent": `digdesign-cli/${CLI_VERSION}`,
2779
- ...sessionId && { "mcp-session-id": sessionId },
2780
- ...protocolVersion && { "mcp-protocol-version": protocolVersion }
2781
- },
2782
- body: line
2783
- });
2784
- } catch (error) {
2785
- return fail(`network error: ${error.message}`);
2786
- }
2787
- sessionId = response.headers.get("mcp-session-id") ?? sessionId;
2788
- if (response.status === 202) return [];
2789
- const body = await response.text();
2790
- if (!response.ok) return fail(`HTTP ${response.status} ${body.slice(0, 200)}`);
2791
- let replies;
2792
- if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) replies = parseSse(body);
2793
- else {
2794
- try {
2795
- replies = body.trim() ? [JSON.parse(body)] : [];
2796
- } catch {
2797
- return fail(`invalid JSON response: ${body.slice(0, 200)}`);
2798
- }
2799
- }
2800
- for (const reply of replies.flat()) {
2801
- const result = reply?.result;
2802
- if (result?.protocolVersion) protocolVersion = result.protocolVersion;
2803
- }
2804
- return replies;
2805
- }
2806
- };
2807
- }
2808
- async function mcpCommand(options) {
2809
- const url = options.url ?? process.env.DIG_MCP_URL ?? DEFAULT_MCP_URL;
2810
- const bridge = createBridge(url);
2811
- const pending = /* @__PURE__ */ new Set();
2812
- process.stderr.write(`dig mcp: stdio bridge to ${url}
2813
- `);
2814
- const input = createInterface2({ input: process.stdin, crlfDelay: Infinity });
2815
- for await (const line of input) {
2816
- if (!line.trim()) continue;
2817
- const task = bridge.handle(line).then((replies) => {
2818
- for (const reply of replies) process.stdout.write(`${JSON.stringify(reply)}
2819
- `);
2820
- });
2821
- pending.add(task);
2822
- void task.finally(() => pending.delete(task));
2823
- }
2824
- await Promise.all(pending);
2825
- }
2826
-
2827
2903
  // src/lib/migration-apply.ts
2828
- import { readFile as readFile12, writeFile as writeFile11 } from "fs/promises";
2829
- import path17 from "path";
2904
+ import { readFile as readFile13, writeFile as writeFile11 } from "fs/promises";
2905
+ import path18 from "path";
2830
2906
 
2831
2907
  // src/lib/migration-strategy.ts
2832
2908
  var TextReplaceStrategy = class {
@@ -2903,7 +2979,7 @@ function looksBinary2(buffer) {
2903
2979
  async function readTextFileSafely2(target) {
2904
2980
  let buffer;
2905
2981
  try {
2906
- buffer = await readFile12(target);
2982
+ buffer = await readFile13(target);
2907
2983
  } catch (error) {
2908
2984
  if (error.code === "ENOENT") return void 0;
2909
2985
  throw error;
@@ -2948,8 +3024,8 @@ async function planOrApplyMigrations(params) {
2948
3024
  };
2949
3025
  }
2950
3026
  const fileTargets = registryItem.files.map((file) => {
2951
- const target = path17.join(cwd, resolveTargetPath(file.path, aliases, paths));
2952
- return { relPath: toPosixPath(path17.relative(cwd, target)), target };
3027
+ const target = path18.join(cwd, resolveTargetPath(file.path, aliases, paths));
3028
+ return { relPath: toPosixPath(path18.relative(cwd, target)), target };
2953
3029
  });
2954
3030
  const currentContents = /* @__PURE__ */ new Map();
2955
3031
  for (const ft of fileTargets) {
@@ -3633,7 +3709,7 @@ ${entry.migrationHint}`);
3633
3709
 
3634
3710
  // src/lib/missing-dependencies.ts
3635
3711
  import { access as access6 } from "fs/promises";
3636
- import path18 from "path";
3712
+ import path19 from "path";
3637
3713
  async function exists2(target) {
3638
3714
  try {
3639
3715
  await access6(target);
@@ -3654,7 +3730,7 @@ async function findMissingDependencies(params) {
3654
3730
  for (const item of tree.values()) {
3655
3731
  if (requested.has(item.name) || lock.components[item.name]) continue;
3656
3732
  for (const file of item.files) {
3657
- const target = path18.join(
3733
+ const target = path19.join(
3658
3734
  cwd,
3659
3735
  resolveTargetPath(file.path, config.aliases, targetPathOptions(config))
3660
3736
  );
@@ -3668,8 +3744,8 @@ async function findMissingDependencies(params) {
3668
3744
  }
3669
3745
 
3670
3746
  // src/lib/update.ts
3671
- import { mkdir as mkdir8, readFile as readFile13, rm as rm2, writeFile as writeFile12 } from "fs/promises";
3672
- import path19 from "path";
3747
+ import { mkdir as mkdir8, readFile as readFile14, rm as rm2, writeFile as writeFile12 } from "fs/promises";
3748
+ import path20 from "path";
3673
3749
  function safeFileIndexes(drift) {
3674
3750
  const indexes = [];
3675
3751
  drift.files.forEach((file, index) => {
@@ -3689,7 +3765,7 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun, paths)
3689
3765
  for (const index of safeIndexes) {
3690
3766
  const file = drift.files[index];
3691
3767
  const registryFile = registryItem.files[index];
3692
- const target = path19.join(cwd, resolveTargetPath(registryFile.path, aliases, paths));
3768
+ const target = path20.join(cwd, resolveTargetPath(registryFile.path, aliases, paths));
3693
3769
  const isNewFile = file.status === "new_upstream";
3694
3770
  pending.push({
3695
3771
  index,
@@ -3697,13 +3773,13 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun, paths)
3697
3773
  newContent: registryFile.content,
3698
3774
  newHash: registryFile.hash ?? hashContent(registryFile.content),
3699
3775
  isNewFile,
3700
- originalContent: isNewFile ? void 0 : await readFile13(target, "utf8")
3776
+ originalContent: isNewFile ? void 0 : await readFile14(target, "utf8")
3701
3777
  });
3702
3778
  }
3703
3779
  const written = [];
3704
3780
  try {
3705
3781
  for (const item of pending) {
3706
- if (item.isNewFile) await mkdir8(path19.dirname(item.target), { recursive: true });
3782
+ if (item.isNewFile) await mkdir8(path20.dirname(item.target), { recursive: true });
3707
3783
  await writeFile12(item.target, item.newContent, "utf8");
3708
3784
  written.push(item);
3709
3785
  }
@@ -4044,6 +4120,7 @@ program.command("init").description(
4044
4120
  program.command("tokens").description("\u062A\u0648\u06A9\u0646\u200C\u0647\u0627\u06CC \u062F\u06CC\u06AF \u062F\u0631 \u0642\u0627\u0644\u0628\u0650 \u0627\u0633\u062A\u0627\u0646\u062F\u0627\u0631\u062F\u0650 DTCG \u0628\u0631\u0627\u06CC \u0641\u06CC\u06AF\u0645\u0627 \u0648 \u0627\u0628\u0632\u0627\u0631\u0647\u0627\u06CC \u0637\u0631\u0627\u062D\u06CC\u060C \u06CC\u06A9 \u0641\u0627\u06CC\u0644 \u0628\u0631\u0627\u06CC \u0647\u0631 \u062D\u0627\u0644\u062A").option("--brand <hex>", "\u0631\u0646\u06AF \u0628\u0631\u0646\u062F\u061B \u067E\u06CC\u0634\u200C\u0641\u0631\u0636 \u0627\u0632 DESIGN.md\u0650 \u067E\u0631\u0648\u0698\u0647").option("--out <dir>", "\u067E\u0648\u0634\u0647\u0654 \u062E\u0631\u0648\u062C\u06CC (\u067E\u06CC\u0634\u200C\u0641\u0631\u0636 tokens)").action(tokensCommand);
4045
4121
  program.command("skill").description("\u06AF\u0630\u0627\u0634\u062A\u0646 \u06CC\u0627 \u0628\u0647\u200C\u0631\u0648\u0632\u06A9\u0631\u062F\u0646\u0650 Skill\u0650 \u0631\u0633\u0645\u06CC\u0650 \u062F\u06CC\u06AF \u0628\u0631\u0627\u06CC Claude Code \u062F\u0631 .claude/skills").action(skillCommand);
4046
4122
  program.command("design-md").description("\u0633\u0627\u062E\u062A\u0646\u0650 DESIGN.md \u062F\u0631 \u0631\u06CC\u0634\u0647\u0654 \u067E\u0631\u0648\u0698\u0647: \u062A\u0648\u06A9\u0646\u200C\u0647\u0627 \u0648 \u0642\u0627\u0639\u062F\u0647\u200C\u0647\u0627\u06CC \u062F\u06CC\u06AF \u0628\u0631\u0627\u06CC \u0627\u06CC\u062C\u0646\u062A\u200C\u0647\u0627\u06CC \u06A9\u062F\u0646\u0648\u06CC\u0633").option("--brand <hex>", "\u0631\u0646\u06AF \u0628\u0631\u0646\u062F \u062A\u0627 \u0631\u0646\u06AF\u200C\u0647\u0627\u06CC \u0648\u0627\u0642\u0639\u06CC\u0650 \u0628\u0631\u0646\u062F \u062F\u0631 \u0641\u0627\u06CC\u0644 \u0628\u06CC\u0627\u06CC\u062F\u060C \u0645\u062B\u0644 #2C4C5B").option("--secondary <hex>", "\u0631\u0646\u06AF \u062F\u0648\u0645\u0650 \u0628\u0631\u0646\u062F").option("--name <name>", "\u0646\u0627\u0645 \u0645\u062D\u0635\u0648\u0644").option("--font <font>", "\u0641\u0648\u0646\u062A: iransans\u060C yekan \u06CC\u0627 vazirmatn").option("--radius <radius>", "\u0634\u0639\u0627\u0639: none\u060C sm\u060C md\u060C lg\u060C xl \u06CC\u0627 full").option("--force", "\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC\u0650 DESIGN.md\u0650 \u062F\u0633\u062A\u200C\u0646\u0648\u0634\u062A\u0647", false).action(designMdCommand);
4123
+ program.command("review").description("\u0628\u0627\u0632\u0628\u06CC\u0646\u06CC\u0650 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u0631\u0627\u0628\u0637 \u0628\u0627 \u0642\u0648\u0627\u0639\u062F \u062F\u06CC\u06AF (\u0647\u0645\u0627\u0646 review_code \u062F\u0631 MCP)\u060C \u0628\u0627 \u0634\u0645\u0627\u0631\u0647\u0654 \u062E\u0637\u0650 \u0647\u0631 \u06CC\u0627\u0641\u062A\u0647").argument("<paths...>", "\u0645\u0633\u06CC\u0631 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC tsx/jsx").option("--url <url>", "\u0622\u062F\u0631\u0633 \u0633\u0631\u0648\u0631 MCP (\u067E\u06CC\u0634\u200C\u0641\u0631\u0636 https://docs.digdesign.ir/api/mcp)").option("--json", "\u062E\u0631\u0648\u062C\u06CC JSON \u0628\u0631\u0627\u06CC \u0627\u0633\u06A9\u0631\u06CC\u067E\u062A/\u0627\u0628\u0632\u0627\u0631", false).action(reviewCommand);
4047
4124
  var theme = program.command("theme").description("\u062A\u0645 \u0648 \u0628\u0631\u0646\u062F: \u062C\u062F\u0627 \u06A9\u0631\u062F\u0646\u0650 \u062A\u0645\u0650 \u062F\u06CC\u06AF \u0627\u0632 \u0628\u0631\u0646\u062F\u0650 \u067E\u0631\u0648\u0698\u0647");
4048
4125
  theme.command("split").description("\u067E\u0631\u0648\u0698\u0647\u0654 \u0642\u062F\u06CC\u0645\u06CC \u0631\u0627 \u0628\u0647 \u0633\u0627\u062E\u062A\u0627\u0631\u0650 \u062F\u0648\u0641\u0627\u06CC\u0644\u06CC \u0645\u06CC\u200C\u0628\u0631\u062F (dig-theme.css \u0645\u0627\u0644\u0650 \u062F\u06CC\u06AF\u060C brand.css \u0645\u0627\u0644\u0650 \u067E\u0631\u0648\u0698\u0647)").option("--dry-run", "\u0641\u0642\u0637 \u0646\u0634\u0627\u0646 \u0628\u062F\u0647 \u0686\u0647 \u0686\u06CC\u0632\u06CC \u0628\u0647 brand.css \u0645\u06CC\u200C\u0631\u0648\u062F\u060C \u0686\u06CC\u0632\u06CC \u0646\u0646\u0648\u06CC\u0633", false).action(themeSplitCommand);
4049
4126
  theme.command("brand").description("\u0631\u0645\u067E\u0650 \u0628\u0631\u0646\u062F \u0631\u0627 \u062F\u0631 brand.css \u0627\u0632 \u06CC\u06A9 \u0631\u0646\u06AF \u0645\u06CC\u200C\u0633\u0627\u0632\u062F\u061B \u0628\u0642\u06CC\u0647\u0654 \u0641\u0627\u06CC\u0644 \u062F\u0633\u062A \u0646\u0645\u06CC\u200C\u062E\u0648\u0631\u062F").argument("<hex>", "\u0631\u0646\u06AF\u0650 \u0627\u0635\u0644\u06CC\u0650 \u0628\u0631\u0646\u062F\u060C \u0645\u062B\u0644 #2C4C5B").action(themeBrandCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "digdesign",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "CLI رسمی برای نصب کامپوننت‌های دیزاین سیستم دیگ",
5
5
  "type": "module",
6
6
  "license": "MIT",