digdesign 0.4.0 → 0.6.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 +425 -80
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/add.ts
7
- import { access as access4, mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
8
- import path6 from "path";
7
+ import { access as access4, mkdir as mkdir3, writeFile as writeFile5 } from "fs/promises";
8
+ import path7 from "path";
9
9
 
10
10
  // src/lib/config.ts
11
11
  import { access, readFile, writeFile } from "fs/promises";
@@ -108,6 +108,16 @@ function parseSelection(raw, options) {
108
108
  }
109
109
  return [...picked];
110
110
  }
111
+ async function select(question, options, defaultValue) {
112
+ const width = String(options.length).length;
113
+ for (const [index, option] of options.entries()) {
114
+ const number = String(index + 1).padStart(width);
115
+ const mark = option.value === defaultValue ? " (\u067E\u06CC\u0634\u200C\u0641\u0631\u0636)" : "";
116
+ console.log(` ${number}) ${option.label}${mark}`);
117
+ }
118
+ const raw = await askLine(`${question} `);
119
+ return parseSelection(raw, options)[0] ?? defaultValue;
120
+ }
111
121
 
112
122
  // src/lib/hash.ts
113
123
  import { createHash } from "crypto";
@@ -403,6 +413,91 @@ function resolveTargetPath(filePath, aliases, options = {}) {
403
413
  return `${aliases.components}/${cleaned}`;
404
414
  }
405
415
 
416
+ // src/commands/design-md.ts
417
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
418
+ import path6 from "path";
419
+ var DESIGN_MD_FILE = "DESIGN.md";
420
+ var GENERATED_MARK = "Generated by Dig";
421
+ async function fetchDesignMd(registryUrl, options) {
422
+ const url = new URL("/api/design-md", registryUrl);
423
+ for (const key2 of ["brand", "secondary", "name", "font", "radius"]) {
424
+ const value = options[key2];
425
+ if (value) url.searchParams.set(key2, value);
426
+ }
427
+ let response;
428
+ try {
429
+ response = await fetch(url);
430
+ } catch {
431
+ throw new DigError(`\u0627\u062A\u0635\u0627\u0644 \u0628\u0647 ${url.origin} \u0628\u0631\u0627\u06CC \u0633\u0627\u062E\u062A\u0646\u0650 DESIGN.md \u0628\u0631\u0642\u0631\u0627\u0631 \u0646\u0634\u062F.`);
432
+ }
433
+ if (!response.ok) {
434
+ const body = await response.json().catch(() => null);
435
+ throw new DigError(`DESIGN.md \u0633\u0627\u062E\u062A\u0647 \u0646\u0634\u062F: ${body?.error ?? response.status}`);
436
+ }
437
+ return response.text();
438
+ }
439
+ async function readIfExists(target) {
440
+ try {
441
+ return await readFile4(target, "utf8");
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
446
+ async function writeDesignMd(cwd, registryUrl, options) {
447
+ const target = path6.join(cwd, DESIGN_MD_FILE);
448
+ const existing = await readIfExists(target);
449
+ if (existing !== null && !options.force) {
450
+ const generated = existing.includes(GENERATED_MARK);
451
+ if (!generated || !options.replaceGenerated) {
452
+ console.log(
453
+ generated ? `- ${DESIGN_MD_FILE} \u0627\u0632 \u0642\u0628\u0644 \u0647\u0633\u062A\u061B \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F. \u0628\u0647\u200C\u0631\u0648\u0632\u0631\u0633\u0627\u0646\u06CC: dig design-md` : `- ${DESIGN_MD_FILE} \u062F\u0633\u062A\u200C\u0646\u0648\u0634\u062A\u0647 \u0627\u0633\u062A\u061B \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F. \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC: dig design-md --force`
454
+ );
455
+ return "skipped";
456
+ }
457
+ }
458
+ await writeFile4(target, await fetchDesignMd(registryUrl, options), "utf8");
459
+ console.log(`\u2713 ${DESIGN_MD_FILE} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F (\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).`);
460
+ return "written";
461
+ }
462
+ function readDesignMdSettings(text) {
463
+ const match = /<!-- dig:design-md (\{.*?\}) -->/.exec(text);
464
+ if (!match) return {};
465
+ try {
466
+ const parsed = JSON.parse(match[1]);
467
+ return Object.fromEntries(
468
+ ["brand", "secondary", "name", "font", "radius"].filter((key2) => typeof parsed[key2] === "string").map((key2) => [key2, parsed[key2]])
469
+ );
470
+ } catch {
471
+ return {};
472
+ }
473
+ }
474
+ async function refreshDesignMd(cwd, registryUrl, overrides = {}, log = console.log) {
475
+ const target = path6.join(cwd, DESIGN_MD_FILE);
476
+ const existing = await readIfExists(target);
477
+ if (existing === null || !existing.includes(GENERATED_MARK)) return "skipped";
478
+ try {
479
+ const next = await fetchDesignMd(registryUrl, { ...readDesignMdSettings(existing), ...overrides });
480
+ if (next === existing) return "unchanged";
481
+ await writeFile4(target, next, "utf8");
482
+ log(`\u2713 ${DESIGN_MD_FILE} \u0647\u0645 \u0628\u0647\u200C\u0631\u0648\u0632 \u0634\u062F.`);
483
+ return "updated";
484
+ } catch {
485
+ log(`- ${DESIGN_MD_FILE} \u0628\u0647\u200C\u0631\u0648\u0632 \u0646\u0634\u062F (\u0627\u062A\u0635\u0627\u0644 \u0628\u0631\u0642\u0631\u0627\u0631 \u0646\u0634\u062F)\u061B \u0628\u0639\u062F\u0627\u064B: dig design-md`);
486
+ return "failed";
487
+ }
488
+ }
489
+ async function designMdCommand(options) {
490
+ const cwd = process.cwd();
491
+ const config = await configExists(cwd) ? await readConfig(cwd) : defaultConfig();
492
+ const previous = readDesignMdSettings(await readIfExists(path6.join(cwd, DESIGN_MD_FILE)) ?? "");
493
+ const explicit = Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0 && value !== false));
494
+ options = { ...previous, ...explicit };
495
+ const result = await writeDesignMd(cwd, config.registryUrl, { ...options, replaceGenerated: true });
496
+ if (result === "written" && !options.brand) {
497
+ console.log(' \u0631\u0646\u06AF \u0628\u0631\u0646\u062F \u062F\u0627\u0631\u06CC\u062F\u061F dig design-md --brand "#2C4C5B" \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.');
498
+ }
499
+ }
500
+
406
501
  // src/commands/add.ts
407
502
  async function pathExists(target) {
408
503
  try {
@@ -444,12 +539,12 @@ async function addCommand(components, options) {
444
539
  console.log(`\u062F\u0631\u06CC\u0627\u0641\u062A \u0627\u0632 ${config.registryUrl} \u2026`);
445
540
  const items = await resolveRegistryTree(names, config.registryUrl);
446
541
  let overwrite = Boolean(options.overwrite);
447
- if (!overwrite) {
542
+ if (!overwrite && !options.skipExisting) {
448
543
  const existing = [];
449
544
  for (const item of items.values()) {
450
545
  for (const file of item.files) {
451
- const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases, targetPathOptions(config)));
452
- if (await pathExists(target)) existing.push(path6.relative(cwd, target));
546
+ const target = path7.join(cwd, resolveTargetPath(file.path, config.aliases, targetPathOptions(config)));
547
+ if (await pathExists(target)) existing.push(path7.relative(cwd, target));
453
548
  }
454
549
  }
455
550
  if (existing.length) {
@@ -470,11 +565,11 @@ ${existing.length} \u0641\u0627\u06CC\u0644 \u0627\u0632 \u0642\u0628\u0644 \u06
470
565
  for (const item of items.values()) {
471
566
  for (const dep of item.dependencies ?? []) npmDeps.add(dep);
472
567
  for (const file of item.files) {
473
- const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases, targetPathOptions(config)));
474
- const exists2 = await pathExists(target);
475
- const mark = exists2 ? overwrite ? "~" : "-" : "+";
476
- const note = exists2 ? overwrite ? " (\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC)" : " (\u0631\u062F \u0645\u06CC\u200C\u0634\u0648\u062F\u060C \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F)" : "";
477
- console.log(`${mark} ${path6.relative(cwd, target)}${note}`);
568
+ const target = path7.join(cwd, resolveTargetPath(file.path, config.aliases, targetPathOptions(config)));
569
+ const exists3 = await pathExists(target);
570
+ const mark = exists3 ? overwrite ? "~" : "-" : "+";
571
+ const note = exists3 ? overwrite ? " (\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC)" : " (\u0631\u062F \u0645\u06CC\u200C\u0634\u0648\u062F\u060C \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F)" : "";
572
+ console.log(`${mark} ${path7.relative(cwd, target)}${note}`);
478
573
  }
479
574
  }
480
575
  console.log(`
@@ -497,17 +592,17 @@ ${items.size} \u0622\u06CC\u062A\u0645 \u0627\u0632 \u0631\u062C\u06CC\u0633\u06
497
592
  const writtenFiles = {};
498
593
  const writtenContents = {};
499
594
  for (const file of item.files) {
500
- const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases, targetPathOptions(config)));
595
+ const target = path7.join(cwd, resolveTargetPath(file.path, config.aliases, targetPathOptions(config)));
501
596
  if (!overwrite && await pathExists(target)) {
502
- console.log(`- \u0631\u062F \u0634\u062F (\u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F): ${path6.relative(cwd, target)}`);
597
+ if (!options.skipExisting) console.log(`- \u0631\u062F \u0634\u062F (\u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F): ${path7.relative(cwd, target)}`);
503
598
  skipped++;
504
599
  continue;
505
600
  }
506
- await mkdir3(path6.dirname(target), { recursive: true });
507
- await writeFile4(target, file.content, "utf8");
508
- console.log(`+ ${path6.relative(cwd, target)}`);
601
+ await mkdir3(path7.dirname(target), { recursive: true });
602
+ await writeFile5(target, file.content, "utf8");
603
+ console.log(`+ ${path7.relative(cwd, target)}`);
509
604
  written++;
510
- const relPath = toPosixPath(path6.relative(cwd, target));
605
+ const relPath = toPosixPath(path7.relative(cwd, target));
511
606
  writtenFiles[relPath] = file.hash ?? hashContent(file.content);
512
607
  writtenContents[relPath] = file.content;
513
608
  }
@@ -530,10 +625,13 @@ ${items.size} \u0622\u06CC\u062A\u0645 \u0627\u0632 \u0631\u062C\u06CC\u0633\u06
530
625
  if (lockChanged) {
531
626
  await writeLock(lock, cwd);
532
627
  console.log(`
533
- \u062B\u0628\u062A \u0634\u062F \u062F\u0631 ${path6.join(".dig", "registry-lock.json")}`);
628
+ \u062B\u0628\u062A \u0634\u062F \u062F\u0631 ${path7.join(".dig", "registry-lock.json")}`);
534
629
  }
535
630
  console.log(`
536
631
  ${written} \u0641\u0627\u06CC\u0644 \u0646\u0648\u0634\u062A\u0647 \u0634\u062F\u060C ${skipped} \u0641\u0627\u06CC\u0644 \u0631\u062F \u0634\u062F.`);
632
+ if (written > 0 && !options.dryRun && !options.skipExisting) {
633
+ await refreshDesignMd(cwd, config.registryUrl);
634
+ }
537
635
  if (!dependencies.size && !devDependencies.size) return;
538
636
  if (options.install === false) {
539
637
  console.log("\n\u0628\u0631\u0627\u06CC \u0646\u0635\u0628 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627 \u062F\u0633\u062A\u06CC \u0627\u062C\u0631\u0627 \u06A9\u0646\u06CC\u062F:");
@@ -654,8 +752,8 @@ async function searchCommand(query, options) {
654
752
  }
655
753
 
656
754
  // src/lib/diff.ts
657
- import { readFile as readFile4 } from "fs/promises";
658
- import path7 from "path";
755
+ import { readFile as readFile5 } from "fs/promises";
756
+ import path8 from "path";
659
757
  var STATUS_PRIORITY = [
660
758
  "conflict",
661
759
  "untracked",
@@ -685,7 +783,7 @@ function overallStatus(files) {
685
783
  }
686
784
  async function readLocalHash(target) {
687
785
  try {
688
- const content = await readFile4(target, "utf8");
786
+ const content = await readFile5(target, "utf8");
689
787
  return hashContent(content);
690
788
  } catch (error) {
691
789
  if (error.code === "ENOENT") return void 0;
@@ -693,8 +791,8 @@ async function readLocalHash(target) {
693
791
  }
694
792
  }
695
793
  async function computeFileDrift(cwd, aliases, lockEntry, file, paths) {
696
- const target = path7.join(cwd, resolveTargetPath(file.path, aliases, paths));
697
- const relPath = toPosixPath(path7.relative(cwd, target));
794
+ const target = path8.join(cwd, resolveTargetPath(file.path, aliases, paths));
795
+ const relPath = toPosixPath(path8.relative(cwd, target));
698
796
  const registryHash = file.hash ?? hashContent(file.content);
699
797
  const installedHash = lockEntry?.files[relPath];
700
798
  const localHash = await readLocalHash(target);
@@ -723,8 +821,8 @@ async function computeComponentDrift(params) {
723
821
  }
724
822
 
725
823
  // src/lib/file-diff-details.ts
726
- import { readFile as readFile5 } from "fs/promises";
727
- import path8 from "path";
824
+ import { readFile as readFile6 } from "fs/promises";
825
+ import path9 from "path";
728
826
 
729
827
  // src/lib/text-diff.ts
730
828
  var DEFAULT_CONTEXT = 3;
@@ -833,7 +931,7 @@ function looksBinary(buffer) {
833
931
  async function readTextFileSafely(target) {
834
932
  let buffer;
835
933
  try {
836
- buffer = await readFile5(target);
934
+ buffer = await readFile6(target);
837
935
  } catch (error) {
838
936
  if (error.code === "ENOENT") return void 0;
839
937
  throw error;
@@ -873,7 +971,7 @@ async function computeFileDiffDetails(params) {
873
971
  note: file.registryHash === file.installedHash ? "\u0641\u0627\u06CC\u0644 \u0628\u0647\u200C\u0635\u0648\u0631\u062A \u0645\u062D\u0644\u06CC \u062D\u0630\u0641 \u0634\u062F\u0647\u061B \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u0627\u0632 \u0632\u0645\u0627\u0646 \u0646\u0635\u0628 \u062A\u063A\u06CC\u06CC\u0631\u06CC \u0646\u06A9\u0631\u062F\u0647." : "\u0641\u0627\u06CC\u0644 \u0628\u0647\u200C\u0635\u0648\u0631\u062A \u0645\u062D\u0644\u06CC \u062D\u0630\u0641 \u0634\u062F\u0647 \u0648 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u0647\u0645 \u062A\u063A\u06CC\u06CC\u0631 \u06A9\u0631\u062F\u0647 \u2014 \u0645\u0642\u0627\u06CC\u0633\u0647\u200C\u0627\u06CC \u0628\u0627 \u0641\u0627\u06CC\u0644 \u062D\u0630\u0641\u200C\u0634\u062F\u0647 \u0645\u0645\u06A9\u0646 \u0646\u06CC\u0633\u062A."
874
972
  };
875
973
  }
876
- const target = path8.join(cwd, resolveTargetPath(registryFile.path, aliases, paths));
974
+ const target = path9.join(cwd, resolveTargetPath(registryFile.path, aliases, paths));
877
975
  const localContent = await readTextFileSafely(target);
878
976
  if (localContent === "binary") {
879
977
  return {
@@ -1430,8 +1528,8 @@ function dependencyRiskLevel(highImpactComponents) {
1430
1528
  }
1431
1529
 
1432
1530
  // src/lib/repo-scanner.ts
1433
- import { readFile as readFile6, readdir } from "fs/promises";
1434
- import path9 from "path";
1531
+ import { readFile as readFile7, readdir } from "fs/promises";
1532
+ import path10 from "path";
1435
1533
  import ts2 from "typescript";
1436
1534
 
1437
1535
  // src/lib/ast-engine.ts
@@ -1506,11 +1604,11 @@ async function collectSourceFiles(root) {
1506
1604
  for (const entry of entries) {
1507
1605
  if (entry.isDirectory()) {
1508
1606
  if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
1509
- await walk(path9.join(dir, entry.name));
1607
+ await walk(path10.join(dir, entry.name));
1510
1608
  continue;
1511
1609
  }
1512
- if (entry.isFile() && SCAN_EXTENSIONS.has(path9.extname(entry.name))) {
1513
- results.push(path9.join(dir, entry.name));
1610
+ if (entry.isFile() && SCAN_EXTENSIONS.has(path10.extname(entry.name))) {
1611
+ results.push(path10.join(dir, entry.name));
1514
1612
  }
1515
1613
  }
1516
1614
  }
@@ -1537,7 +1635,7 @@ async function scanRepositoryForComponent(params) {
1537
1635
  for (const filePath of sourceFilePaths) {
1538
1636
  let content;
1539
1637
  try {
1540
- content = await readFile6(filePath, "utf8");
1638
+ content = await readFile7(filePath, "utf8");
1541
1639
  } catch {
1542
1640
  continue;
1543
1641
  }
@@ -1551,7 +1649,7 @@ async function scanRepositoryForComponent(params) {
1551
1649
  const snippets = findJsxUsagesInSource(sourceFile, tagName);
1552
1650
  if (snippets.length === 0) continue;
1553
1651
  files.push({
1554
- path: toPosixPath(path9.relative(projectRoot, filePath)),
1652
+ path: toPosixPath(path10.relative(projectRoot, filePath)),
1555
1653
  usages: snippets.length,
1556
1654
  snippets
1557
1655
  });
@@ -1957,12 +2055,74 @@ async function impactCommand(name, options) {
1957
2055
  }
1958
2056
 
1959
2057
  // src/commands/init.ts
2058
+ import { readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
2059
+ import path14 from "path";
2060
+
2061
+ // src/lib/agent-instructions.ts
1960
2062
  import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
1961
- import path12 from "path";
2063
+ import path11 from "path";
2064
+ var AGENT_CLIENTS = [
2065
+ { value: "claude-code", label: "Claude Code (CLAUDE.md)" },
2066
+ { value: "codex", label: "Codex (AGENTS.md)" },
2067
+ { value: "cursor", label: "Cursor (AGENTS.md)" },
2068
+ { value: "vscode", label: "VS Code (AGENTS.md)" },
2069
+ { value: "gemini", label: "Gemini CLI (GEMINI.md)" }
2070
+ ];
2071
+ var FONTS = [
2072
+ { value: "iransans", label: "IRANSansX" },
2073
+ { value: "yekan", label: "IRANYekan" },
2074
+ { value: "vazirmatn", label: "Vazirmatn" }
2075
+ ];
2076
+ var BLOCK_START = "<!-- dig:start";
2077
+ var BLOCK_END = "<!-- dig:end -->";
2078
+ async function fetchInstructionsFile(registryUrl, options) {
2079
+ const url = new URL("/api/agent-setup", registryUrl);
2080
+ if (options.agent) url.searchParams.set("client", options.agent);
2081
+ for (const key2 of ["brand", "font", "name"]) {
2082
+ const value = options[key2];
2083
+ if (value) url.searchParams.set(key2, value);
2084
+ }
2085
+ let response;
2086
+ try {
2087
+ response = await fetch(url);
2088
+ } catch {
2089
+ throw new DigError(`\u0627\u062A\u0635\u0627\u0644 \u0628\u0647 ${url.origin} \u0628\u0631\u0627\u06CC \u0633\u0627\u062E\u062A\u0646\u0650 \u0641\u0627\u06CC\u0644\u0650 \u0627\u06CC\u062C\u0646\u062A \u0628\u0631\u0642\u0631\u0627\u0631 \u0646\u0634\u062F.`);
2090
+ }
2091
+ const body = await response.json().catch(() => null);
2092
+ const file = body?.files?.find((item) => /\.md$/.test(item.path) && item.path !== "DESIGN.md");
2093
+ if (!response.ok || !file) {
2094
+ throw new DigError(`\u0641\u0627\u06CC\u0644\u0650 \u0627\u06CC\u062C\u0646\u062A \u0633\u0627\u062E\u062A\u0647 \u0646\u0634\u062F: ${body?.error ?? response.status}`);
2095
+ }
2096
+ return file;
2097
+ }
2098
+ function mergeInstructions(existing, block) {
2099
+ const trimmed = block.trim();
2100
+ if (existing === null || existing.trim() === "") return `${trimmed}
2101
+ `;
2102
+ const start = existing.indexOf(BLOCK_START);
2103
+ const end = existing.indexOf(BLOCK_END);
2104
+ if (start >= 0 && end > start) {
2105
+ return `${existing.slice(0, start)}${trimmed}${existing.slice(end + BLOCK_END.length)}`;
2106
+ }
2107
+ return `${existing.trimEnd()}
2108
+
2109
+ ${trimmed}
2110
+ `;
2111
+ }
2112
+ async function writeAgentInstructions(cwd, registryUrl, options) {
2113
+ const file = await fetchInstructionsFile(registryUrl, options);
2114
+ const target = path11.join(cwd, file.path);
2115
+ const existing = await readFile8(target, "utf8").catch(() => null);
2116
+ await writeFile6(target, mergeInstructions(existing, file.content), "utf8");
2117
+ console.log(
2118
+ existing === null ? `\u2713 ${file.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 ${file.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.`
2119
+ );
2120
+ return file.path;
2121
+ }
1962
2122
 
1963
2123
  // src/commands/theme.ts
1964
- import { access as access5, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
1965
- import path11 from "path";
2124
+ import { access as access5, mkdir as mkdir4, readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
2125
+ import path13 from "path";
1966
2126
 
1967
2127
  // src/lib/brand-ramp.ts
1968
2128
  var RAMP_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
@@ -2041,6 +2201,9 @@ function relativeLuminance(rgb) {
2041
2201
  function contrastWithWhite(color) {
2042
2202
  return 1.05 / (relativeLuminance(oklchToSrgb(color)) + 0.05);
2043
2203
  }
2204
+ function contrastWithBlack(color) {
2205
+ return (relativeLuminance(oklchToSrgb(color)) + 0.05) / 0.05;
2206
+ }
2044
2207
  function formatOklch({ l, c, h }) {
2045
2208
  return `oklch(${l.toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(1)})`;
2046
2209
  }
@@ -2063,9 +2226,9 @@ function buildBrandRamp(hex) {
2063
2226
  }
2064
2227
 
2065
2228
  // src/lib/theme-files.ts
2066
- import path10 from "path";
2229
+ import path12 from "path";
2067
2230
  function importPath(fromFile, toFile) {
2068
- const rel = toPosixPath(path10.relative(path10.dirname(fromFile), toFile));
2231
+ const rel = toPosixPath(path12.relative(path12.dirname(fromFile), toFile));
2069
2232
  return rel.startsWith(".") ? rel : `./${rel}`;
2070
2233
  }
2071
2234
  function globalsCss(paths, extraStatements = []) {
@@ -2096,10 +2259,14 @@ var BRAND_HEADER = `/* =========================================================
2096
2259
  `;
2097
2260
  function brandRampBlock(ramp, hex) {
2098
2261
  const lines = RAMP_STEPS.map((step) => ` --brand-${step}: ${formatOklch(ramp.steps[step])};`);
2262
+ const darkText = contrastWithWhite(ramp.steps[400]) >= contrastWithBlack(ramp.steps[400]) ? "--dig-white" : "--dig-black";
2099
2263
  return `/* dig:brand-ramp-start \u2014 \u0633\u0627\u062E\u062A\u0647\u200C\u0634\u062F\u0647 \u0627\u0632 ${hex.toUpperCase()}\u061B \xABdig theme brand\xBB \u0647\u0645\u06CC\u0646 \u0628\u0644\u0648\u06A9 \u0631\u0627 \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u0645\u06CC\u200C\u06A9\u0646\u062F */
2100
2264
  :root {
2101
2265
  ${lines.join("\n")}
2102
2266
  }
2267
+ .dark {
2268
+ --primary-foreground: var(${darkText});
2269
+ }
2103
2270
  /* dig:brand-ramp-end */`;
2104
2271
  }
2105
2272
  function brandCss(options) {
@@ -2224,9 +2391,9 @@ function splitTheme(projectCss, digThemeCss) {
2224
2391
 
2225
2392
  // src/commands/theme.ts
2226
2393
  var THEME_ITEM = "theme";
2227
- async function readIfExists(target) {
2394
+ async function readIfExists2(target) {
2228
2395
  try {
2229
- return await readFile7(target, "utf8");
2396
+ return await readFile9(target, "utf8");
2230
2397
  } catch {
2231
2398
  return null;
2232
2399
  }
@@ -2240,7 +2407,7 @@ async function exists(target) {
2240
2407
  }
2241
2408
  }
2242
2409
  function themePaths(config) {
2243
- const dir = path11.posix.dirname(toPosixPath(config.tailwind.css));
2410
+ const dir = path13.posix.dirname(toPosixPath(config.tailwind.css));
2244
2411
  return {
2245
2412
  css: config.tailwind.css,
2246
2413
  theme: config.tailwind.theme ?? `${dir}/dig-theme.css`,
@@ -2274,9 +2441,9 @@ async function recordTheme(cwd, config, themeRel, item) {
2274
2441
  await writeLock(lock, cwd);
2275
2442
  }
2276
2443
  async function writeText(cwd, rel, content) {
2277
- const target = path11.join(cwd, rel);
2278
- await mkdir4(path11.dirname(target), { recursive: true });
2279
- await writeFile5(target, content, "utf8");
2444
+ const target = path13.join(cwd, rel);
2445
+ await mkdir4(path13.dirname(target), { recursive: true });
2446
+ await writeFile7(target, content, "utf8");
2280
2447
  }
2281
2448
  async function installTheme(cwd, config, options) {
2282
2449
  const paths = themePaths(config);
@@ -2284,7 +2451,7 @@ async function installTheme(cwd, config, options) {
2284
2451
  const ramp = options.brand ? buildBrandRamp(options.brand) : void 0;
2285
2452
  await writeText(cwd, paths.theme, item.content);
2286
2453
  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).`);
2287
- const existingBrand = await readIfExists(path11.join(cwd, paths.brand));
2454
+ const existingBrand = await readIfExists2(path13.join(cwd, paths.brand));
2288
2455
  if (existingBrand === null) {
2289
2456
  await writeText(cwd, paths.brand, brandCss({ ramp, hex: options.brand }));
2290
2457
  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).`);
@@ -2295,10 +2462,10 @@ async function installTheme(cwd, config, options) {
2295
2462
  console.log(`- ${paths.brand} \u0627\u0632 \u0642\u0628\u0644 \u0647\u0633\u062A\u061B \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F.`);
2296
2463
  }
2297
2464
  if (ramp) reportContrast(ramp.primaryContrast);
2298
- const cssTarget = path11.join(cwd, paths.css);
2299
- const existingCss = await readIfExists(cssTarget);
2465
+ const cssTarget = path13.join(cwd, paths.css);
2466
+ const existingCss = await readIfExists2(cssTarget);
2300
2467
  if (!isBoilerplateCss(existingCss) && existingCss !== null) {
2301
- await writeFile5(`${cssTarget}.backup`, existingCss, "utf8");
2468
+ await writeFile7(`${cssTarget}.backup`, existingCss, "utf8");
2302
2469
  console.log(` \u0646\u0633\u062E\u0647\u0654 \u0642\u0628\u0644\u06CC\u0650 ${paths.css} \u062F\u0631 ${paths.css}.backup \u0646\u06AF\u0647 \u062F\u0627\u0634\u062A\u0647 \u0634\u062F.`);
2303
2470
  console.log(` \u0627\u06AF\u0631 \u0622\u0646 \u0641\u0627\u06CC\u0644 \u062A\u0645\u0650 \u0642\u062F\u06CC\u0645\u06CC\u0650 \u062F\u06CC\u06AF \u0628\u0648\u062F\u060C \u0628\u0647\u200C\u062C\u0627\u06CC init \u0627\u0632 \xABdig theme split\xBB \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u06A9\u0646\u06CC\u062F.`);
2304
2471
  }
@@ -2331,11 +2498,11 @@ async function themeSplitCommand(options) {
2331
2498
  return;
2332
2499
  }
2333
2500
  const paths = themePaths(config);
2334
- const current = await readIfExists(path11.join(cwd, paths.css));
2501
+ const current = await readIfExists2(path13.join(cwd, paths.css));
2335
2502
  if (current === null || isBoilerplateCss(current)) {
2336
2503
  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.`);
2337
2504
  }
2338
- if (await exists(path11.join(cwd, paths.brand))) {
2505
+ if (await exists(path13.join(cwd, paths.brand))) {
2339
2506
  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.`);
2340
2507
  }
2341
2508
  console.log(`\u062F\u0631\u06CC\u0627\u0641\u062A\u0650 \u062A\u0645\u0650 \u062F\u06CC\u06AF \u0627\u0632 ${config.registryUrl} \u2026`);
@@ -2354,7 +2521,7 @@ ${split.changed} \u062A\u0648\u06A9\u0646\u0650 \u0639\u0648\u0636\u200C\u0634\u
2354
2521
  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)");
2355
2522
  return;
2356
2523
  }
2357
- await writeFile5(path11.join(cwd, `${paths.css}.backup`), current, "utf8");
2524
+ await writeFile7(path13.join(cwd, `${paths.css}.backup`), current, "utf8");
2358
2525
  await writeText(cwd, paths.theme, item.content);
2359
2526
  await writeText(cwd, paths.brand, brandCss({ body: split.body }));
2360
2527
  await writeText(cwd, paths.css, globalsCss(paths, split.extraStatements));
@@ -2378,11 +2545,12 @@ async function themeBrandCommand(hex) {
2378
2545
  }
2379
2546
  const paths = themePaths(config);
2380
2547
  const ramp = buildBrandRamp(hex);
2381
- const existing = await readIfExists(path11.join(cwd, paths.brand)) ?? brandCss({});
2548
+ const existing = await readIfExists2(path13.join(cwd, paths.brand)) ?? brandCss({});
2382
2549
  const next = replaceBrandRamp(existing, ramp, hex);
2383
2550
  await writeText(cwd, paths.brand, next);
2384
2551
  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).`);
2385
2552
  reportContrast(ramp.primaryContrast);
2553
+ await refreshDesignMd(cwd, config.registryUrl, { brand: hex });
2386
2554
  const outside = next.replace(/\/\* dig:brand-ramp-start[\s\S]*?\/\* dig:brand-ramp-end \*\//, "");
2387
2555
  const shadowing = outside.match(/--brand-\d+\s*:/g);
2388
2556
  if (shadowing) {
@@ -2393,16 +2561,16 @@ async function themeBrandCommand(hex) {
2393
2561
  }
2394
2562
 
2395
2563
  // src/commands/init.ts
2396
- async function readIfExists2(target) {
2564
+ async function readIfExists3(target) {
2397
2565
  try {
2398
- return await readFile8(target, "utf8");
2566
+ return await readFile10(target, "utf8");
2399
2567
  } catch {
2400
2568
  return null;
2401
2569
  }
2402
2570
  }
2403
2571
  async function writeTheme(config, cwd, options) {
2404
2572
  const paths = themePaths(config);
2405
- const existing = await readIfExists2(path12.join(cwd, paths.css));
2573
+ const existing = await readIfExists3(path14.join(cwd, paths.css));
2406
2574
  if (!isBoilerplateCss(existing) && !options.yes) {
2407
2575
  console.log(`
2408
2576
  ${paths.css} \u0627\u0632 \u0642\u0628\u0644 \u0645\u062D\u062A\u0648\u0627 \u062F\u0627\u0631\u062F.`);
@@ -2427,8 +2595,8 @@ ${paths.css} \u0627\u0632 \u0642\u0628\u0644 \u0645\u062D\u062A\u0648\u0627 \u06
2427
2595
  }
2428
2596
  async function applyRtl(cwd) {
2429
2597
  for (const candidate of ["src/app/layout.tsx", "app/layout.tsx"]) {
2430
- const target = path12.join(cwd, candidate);
2431
- const source = await readIfExists2(target);
2598
+ const target = path14.join(cwd, candidate);
2599
+ const source = await readIfExists3(target);
2432
2600
  if (source === null) continue;
2433
2601
  if (/<html[^>]*\sdir=/.test(source)) {
2434
2602
  console.log(`- ${candidate} \u0627\u0632 \u0642\u0628\u0644 dir \u062F\u0627\u0631\u062F\u061B \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F.`);
@@ -2440,7 +2608,7 @@ async function applyRtl(cwd) {
2440
2608
  match[0],
2441
2609
  `<html${match[1]} dir="rtl">`.replace(/\s+dir="rtl">$/, ' dir="rtl">')
2442
2610
  );
2443
- await writeFile6(target, updated, "utf8");
2611
+ await writeFile8(target, updated, "utf8");
2444
2612
  console.log(`\u2713 dir="rtl" \u0628\u0647 ${candidate} \u0627\u0636\u0627\u0641\u0647 \u0634\u062F.`);
2445
2613
  return;
2446
2614
  }
@@ -2448,6 +2616,15 @@ async function applyRtl(cwd) {
2448
2616
  '- \u0686\u06CC\u062F\u0645\u0627\u0646\u0650 \u0631\u06CC\u0634\u0647 \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F. \u062E\u0648\u062F\u062A\u0627\u0646 dir="rtl" \u0631\u0627 \u0631\u0648\u06CC \u062A\u06AF <html> \u0628\u06AF\u0630\u0627\u0631\u06CC\u062F.'
2449
2617
  );
2450
2618
  }
2619
+ async function askProjectDecisions(options) {
2620
+ if (options.yes) return { brand: options.brand, font: options.font, agent: options.agent };
2621
+ const brand = options.brand ?? ((await ask("\u0631\u0646\u06AF \u0628\u0631\u0646\u062F (\u06A9\u062F \u0647\u06AF\u0632\u061B Enter \u06CC\u0639\u0646\u06CC \u0631\u0646\u06AF \u067E\u06CC\u0634\u200C\u0641\u0631\u0636 \u062F\u06CC\u06AF)\u061F", "")).trim() || void 0);
2622
+ console.log("\n\u0641\u0648\u0646\u062A \u0641\u0627\u0631\u0633\u06CC:");
2623
+ const font = options.font ?? await select("\u0634\u0645\u0627\u0631\u0647\u0654 \u0641\u0648\u0646\u062A\u061F", FONTS, "iransans");
2624
+ console.log("\n\u0628\u0627 \u06A9\u062F\u0627\u0645 \u0627\u06CC\u062C\u0646\u062A \u06A9\u062F \u0645\u06CC\u200C\u0646\u0648\u06CC\u0633\u06CC\u062F\u061F");
2625
+ const agent = options.agent ?? await select("\u0634\u0645\u0627\u0631\u0647\u0654 \u0627\u06CC\u062C\u0646\u062A\u061F", AGENT_CLIENTS, "codex");
2626
+ return { brand, font, agent };
2627
+ }
2451
2628
  async function initCommand(options) {
2452
2629
  const cwd = process.cwd();
2453
2630
  const base = defaultConfig();
@@ -2463,16 +2640,24 @@ async function initCommand(options) {
2463
2640
  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),
2464
2641
  tailwind: await (async () => {
2465
2642
  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);
2466
- const dir = path12.posix.dirname(css.split(path12.sep).join("/"));
2643
+ const dir = path14.posix.dirname(css.split(path14.sep).join("/"));
2467
2644
  return { ...base.tailwind, css, theme: `${dir}/dig-theme.css`, brand: `${dir}/brand.css` };
2468
2645
  })()
2469
2646
  };
2647
+ const decisions = await askProjectDecisions(options);
2648
+ options = { ...options, brand: decisions.brand };
2470
2649
  await writeConfig(config, cwd);
2471
- console.log(`\u2713 ${path12.join(cwd, "dig.json")} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F.`);
2650
+ console.log(`\u2713 ${path14.join(cwd, "dig.json")} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F.`);
2472
2651
  const theme2 = await writeTheme(config, cwd, options);
2473
2652
  if (config.rtl) {
2474
2653
  await applyRtl(cwd);
2475
2654
  }
2655
+ await writeDesignMd(cwd, config.registryUrl, { brand: decisions.brand, font: decisions.font }).catch((error) => {
2656
+ console.log(`- DESIGN.md \u0633\u0627\u062E\u062A\u0647 \u0646\u0634\u062F (${error instanceof Error ? error.message : error}). \u0628\u0639\u062F\u0627\u064B: dig design-md`);
2657
+ });
2658
+ await writeAgentInstructions(cwd, config.registryUrl, decisions).catch((error) => {
2659
+ console.log(`- \u0641\u0627\u06CC\u0644\u0650 \u0627\u06CC\u062C\u0646\u062A \u0633\u0627\u062E\u062A\u0647 \u0646\u0634\u062F (${error instanceof Error ? error.message : error}). \u0628\u0639\u062F\u0627\u064B: dig init`);
2660
+ });
2476
2661
  console.log("\n\u0622\u0645\u0627\u062F\u0647 \u0627\u0633\u062A. \u0642\u062F\u0645 \u0628\u0639\u062F\u06CC:");
2477
2662
  console.log(" dig add button");
2478
2663
  if (theme2 === "written") {
@@ -2486,9 +2671,102 @@ async function initCommand(options) {
2486
2671
  }
2487
2672
  }
2488
2673
 
2674
+ // src/commands/mcp.ts
2675
+ import { createInterface as createInterface2 } from "readline";
2676
+
2677
+ // src/lib/version.ts
2678
+ var CLI_VERSION = "0.6.0";
2679
+
2680
+ // src/commands/mcp.ts
2681
+ var DEFAULT_MCP_URL = "https://docs.digdesign.ir/api/mcp";
2682
+ function parseSse(body) {
2683
+ const messages = [];
2684
+ for (const event of body.split(/\r?\n\r?\n/)) {
2685
+ const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
2686
+ if (!data) continue;
2687
+ try {
2688
+ messages.push(JSON.parse(data));
2689
+ } catch {
2690
+ }
2691
+ }
2692
+ return messages;
2693
+ }
2694
+ function requestIds(message) {
2695
+ const list = Array.isArray(message) ? message : [message];
2696
+ 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);
2697
+ }
2698
+ function createBridge(url, fetchImpl = fetch) {
2699
+ let sessionId;
2700
+ let protocolVersion;
2701
+ return {
2702
+ async handle(line) {
2703
+ let message;
2704
+ try {
2705
+ message = JSON.parse(line);
2706
+ } catch {
2707
+ return [{ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }];
2708
+ }
2709
+ const ids = requestIds(message);
2710
+ const fail = (reason) => ids.map((id) => ({ jsonrpc: "2.0", id, error: { code: -32603, message: `Dig MCP (${url}): ${reason}` } }));
2711
+ let response;
2712
+ try {
2713
+ response = await fetchImpl(url, {
2714
+ method: "POST",
2715
+ headers: {
2716
+ "content-type": "application/json",
2717
+ accept: "application/json, text/event-stream",
2718
+ "user-agent": `digdesign-cli/${CLI_VERSION}`,
2719
+ ...sessionId && { "mcp-session-id": sessionId },
2720
+ ...protocolVersion && { "mcp-protocol-version": protocolVersion }
2721
+ },
2722
+ body: line
2723
+ });
2724
+ } catch (error) {
2725
+ return fail(`network error: ${error.message}`);
2726
+ }
2727
+ sessionId = response.headers.get("mcp-session-id") ?? sessionId;
2728
+ if (response.status === 202) return [];
2729
+ const body = await response.text();
2730
+ if (!response.ok) return fail(`HTTP ${response.status} ${body.slice(0, 200)}`);
2731
+ let replies;
2732
+ if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) replies = parseSse(body);
2733
+ else {
2734
+ try {
2735
+ replies = body.trim() ? [JSON.parse(body)] : [];
2736
+ } catch {
2737
+ return fail(`invalid JSON response: ${body.slice(0, 200)}`);
2738
+ }
2739
+ }
2740
+ for (const reply of replies.flat()) {
2741
+ const result = reply?.result;
2742
+ if (result?.protocolVersion) protocolVersion = result.protocolVersion;
2743
+ }
2744
+ return replies;
2745
+ }
2746
+ };
2747
+ }
2748
+ async function mcpCommand(options) {
2749
+ const url = options.url ?? process.env.DIG_MCP_URL ?? DEFAULT_MCP_URL;
2750
+ const bridge = createBridge(url);
2751
+ const pending = /* @__PURE__ */ new Set();
2752
+ process.stderr.write(`dig mcp: stdio bridge to ${url}
2753
+ `);
2754
+ const input = createInterface2({ input: process.stdin, crlfDelay: Infinity });
2755
+ for await (const line of input) {
2756
+ if (!line.trim()) continue;
2757
+ const task = bridge.handle(line).then((replies) => {
2758
+ for (const reply of replies) process.stdout.write(`${JSON.stringify(reply)}
2759
+ `);
2760
+ });
2761
+ pending.add(task);
2762
+ void task.finally(() => pending.delete(task));
2763
+ }
2764
+ await Promise.all(pending);
2765
+ }
2766
+
2489
2767
  // src/lib/migration-apply.ts
2490
- import { readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
2491
- import path13 from "path";
2768
+ import { readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
2769
+ import path15 from "path";
2492
2770
 
2493
2771
  // src/lib/migration-strategy.ts
2494
2772
  var TextReplaceStrategy = class {
@@ -2565,7 +2843,7 @@ function looksBinary2(buffer) {
2565
2843
  async function readTextFileSafely2(target) {
2566
2844
  let buffer;
2567
2845
  try {
2568
- buffer = await readFile9(target);
2846
+ buffer = await readFile11(target);
2569
2847
  } catch (error) {
2570
2848
  if (error.code === "ENOENT") return void 0;
2571
2849
  throw error;
@@ -2610,8 +2888,8 @@ async function planOrApplyMigrations(params) {
2610
2888
  };
2611
2889
  }
2612
2890
  const fileTargets = registryItem.files.map((file) => {
2613
- const target = path13.join(cwd, resolveTargetPath(file.path, aliases, paths));
2614
- return { relPath: toPosixPath(path13.relative(cwd, target)), target };
2891
+ const target = path15.join(cwd, resolveTargetPath(file.path, aliases, paths));
2892
+ return { relPath: toPosixPath(path15.relative(cwd, target)), target };
2615
2893
  });
2616
2894
  const currentContents = /* @__PURE__ */ new Map();
2617
2895
  for (const ft of fileTargets) {
@@ -2677,12 +2955,12 @@ async function planOrApplyMigrations(params) {
2677
2955
  const written = [];
2678
2956
  try {
2679
2957
  for (const item of pending) {
2680
- await writeFile7(item.target, item.newContent, "utf8");
2958
+ await writeFile9(item.target, item.newContent, "utf8");
2681
2959
  written.push(item);
2682
2960
  }
2683
2961
  } catch (error) {
2684
2962
  for (const item of written) {
2685
- await writeFile7(item.target, item.originalContent, "utf8");
2963
+ await writeFile9(item.target, item.originalContent, "utf8");
2686
2964
  }
2687
2965
  return {
2688
2966
  name,
@@ -3293,9 +3571,45 @@ ${entry.migrationHint}`);
3293
3571
  }
3294
3572
  }
3295
3573
 
3574
+ // src/lib/missing-dependencies.ts
3575
+ import { access as access6 } from "fs/promises";
3576
+ import path16 from "path";
3577
+ async function exists2(target) {
3578
+ try {
3579
+ await access6(target);
3580
+ return true;
3581
+ } catch {
3582
+ return false;
3583
+ }
3584
+ }
3585
+ async function findMissingDependencies(params) {
3586
+ const { cwd, config, lock, items } = params;
3587
+ const requested = new Set(items.map((item) => item.name));
3588
+ const direct = [
3589
+ ...new Set(items.flatMap((item) => item.registryDependencies ?? []))
3590
+ ].filter((name) => !requested.has(name));
3591
+ if (direct.length === 0) return [];
3592
+ const tree = await resolveRegistryTree(direct, config.registryUrl);
3593
+ const missing = [];
3594
+ for (const item of tree.values()) {
3595
+ if (requested.has(item.name) || lock.components[item.name]) continue;
3596
+ for (const file of item.files) {
3597
+ const target = path16.join(
3598
+ cwd,
3599
+ resolveTargetPath(file.path, config.aliases, targetPathOptions(config))
3600
+ );
3601
+ if (!await exists2(target)) {
3602
+ missing.push(item);
3603
+ break;
3604
+ }
3605
+ }
3606
+ }
3607
+ return missing;
3608
+ }
3609
+
3296
3610
  // src/lib/update.ts
3297
- import { mkdir as mkdir5, readFile as readFile10, rm as rm2, writeFile as writeFile8 } from "fs/promises";
3298
- import path14 from "path";
3611
+ import { mkdir as mkdir5, readFile as readFile12, rm as rm2, writeFile as writeFile10 } from "fs/promises";
3612
+ import path17 from "path";
3299
3613
  function safeFileIndexes(drift) {
3300
3614
  const indexes = [];
3301
3615
  drift.files.forEach((file, index) => {
@@ -3315,7 +3629,7 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun, paths)
3315
3629
  for (const index of safeIndexes) {
3316
3630
  const file = drift.files[index];
3317
3631
  const registryFile = registryItem.files[index];
3318
- const target = path14.join(cwd, resolveTargetPath(registryFile.path, aliases, paths));
3632
+ const target = path17.join(cwd, resolveTargetPath(registryFile.path, aliases, paths));
3319
3633
  const isNewFile = file.status === "new_upstream";
3320
3634
  pending.push({
3321
3635
  index,
@@ -3323,14 +3637,14 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun, paths)
3323
3637
  newContent: registryFile.content,
3324
3638
  newHash: registryFile.hash ?? hashContent(registryFile.content),
3325
3639
  isNewFile,
3326
- originalContent: isNewFile ? void 0 : await readFile10(target, "utf8")
3640
+ originalContent: isNewFile ? void 0 : await readFile12(target, "utf8")
3327
3641
  });
3328
3642
  }
3329
3643
  const written = [];
3330
3644
  try {
3331
3645
  for (const item of pending) {
3332
- if (item.isNewFile) await mkdir5(path14.dirname(item.target), { recursive: true });
3333
- await writeFile8(item.target, item.newContent, "utf8");
3646
+ if (item.isNewFile) await mkdir5(path17.dirname(item.target), { recursive: true });
3647
+ await writeFile10(item.target, item.newContent, "utf8");
3334
3648
  written.push(item);
3335
3649
  }
3336
3650
  } catch (error) {
@@ -3338,7 +3652,7 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun, paths)
3338
3652
  if (item.isNewFile) {
3339
3653
  await rm2(item.target, { force: true });
3340
3654
  } else {
3341
- await writeFile8(item.target, item.originalContent, "utf8");
3655
+ await writeFile10(item.target, item.originalContent, "utf8");
3342
3656
  }
3343
3657
  }
3344
3658
  return { actions, writtenHashes: {}, rollbackError: error.message };
@@ -3452,6 +3766,33 @@ async function updateCommand(components, options) {
3452
3766
  if (entries.some((entry) => entry.result.lockUpdated)) {
3453
3767
  await writeLock(lock, cwd);
3454
3768
  }
3769
+ let missing = [];
3770
+ try {
3771
+ missing = (await findMissingDependencies({
3772
+ cwd,
3773
+ config,
3774
+ lock,
3775
+ items: entries.filter((entry) => entry.result.tracked && entry.registryItem).map((entry) => entry.registryItem)
3776
+ })).map((item) => item.name);
3777
+ } catch (error) {
3778
+ console.error(`\u2717 \u0628\u0631\u0631\u0633\u06CC\u0650 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627\u06CC \u062A\u0627\u0632\u0647: ${error.message}`);
3779
+ }
3780
+ if (missing.length && !dryRun) {
3781
+ if (!options.json) {
3782
+ console.log(`
3783
+ \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627\u06CC \u062A\u0627\u0632\u0647\u200C\u0627\u06CC \u06A9\u0647 \u067E\u0631\u0648\u0698\u0647 \u0646\u062F\u0627\u0634\u062A \u0646\u0635\u0628 \u0645\u06CC\u200C\u0634\u0648\u0646\u062F: ${missing.join("\u060C ")}`);
3784
+ }
3785
+ const log = console.log;
3786
+ if (options.json) console.log = console.error;
3787
+ try {
3788
+ await addCommand(missing, { skipExisting: true });
3789
+ } finally {
3790
+ console.log = log;
3791
+ }
3792
+ }
3793
+ if (!dryRun && entries.length) {
3794
+ await refreshDesignMd(cwd, config.registryUrl, {}, options.json ? console.error : console.log);
3795
+ }
3455
3796
  if (options.json) {
3456
3797
  console.log(
3457
3798
  JSON.stringify(
@@ -3466,7 +3807,8 @@ async function updateCommand(components, options) {
3466
3807
  action: result.actions?.[index] ?? "skipped"
3467
3808
  })),
3468
3809
  ...result.snapshotError ? { snapshotError: result.snapshotError } : {}
3469
- }))
3810
+ })),
3811
+ [dryRun ? "missingDependencies" : "addedDependencies"]: missing
3470
3812
  },
3471
3813
  null,
3472
3814
  2
@@ -3484,6 +3826,10 @@ async function updateCommand(components, options) {
3484
3826
  await printUpdateResult(cwd, config, entry);
3485
3827
  }
3486
3828
  }
3829
+ if (missing.length && dryRun) {
3830
+ console.log(`
3831
+ \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627\u06CC \u062A\u0627\u0632\u0647\u200C\u0627\u06CC \u06A9\u0647 \u0646\u0635\u0628 \u0645\u06CC\u200C\u0634\u062F\u0646\u062F: ${missing.join("\u060C ")}`);
3832
+ }
3487
3833
  }
3488
3834
  function printUpdateSummary(entries) {
3489
3835
  console.log("\u062E\u0644\u0627\u0635\u0647\u0654 \u0628\u0631\u0648\u0632\u0631\u0633\u0627\u0646\u06CC");
@@ -3629,18 +3975,17 @@ ${result.name}`);
3629
3975
  }
3630
3976
  }
3631
3977
 
3632
- // src/lib/version.ts
3633
- var CLI_VERSION = "0.4.0";
3634
-
3635
3978
  // src/index.ts
3636
3979
  var program = new Command();
3637
3980
  program.name("dig").description("CLI \u0631\u0633\u0645\u06CC \u062F\u06CC\u0632\u0627\u06CC\u0646 \u0633\u06CC\u0633\u062A\u0645 \u062F\u06CC\u06AF").version(CLI_VERSION);
3638
3981
  program.command("init").description(
3639
- '\u0631\u0627\u0647\u200C\u0627\u0646\u062F\u0627\u0632\u06CC \u062F\u06CC\u06AF \u062F\u0631 \u067E\u0631\u0648\u0698\u0647: dig.json\u060C \u062A\u0645 \u0648 \u062A\u0648\u06A9\u0646\u200C\u0647\u0627\u060C \u0648 dir="rtl"'
3640
- ).option("--yes", "\u067E\u0627\u0633\u062E \u067E\u06CC\u0634\u200C\u0641\u0631\u0636 \u0628\u0647 \u0647\u0645\u0647\u0654 \u0633\u0648\u0627\u0644\u0627\u062A", false).option("--brand <hex>", "\u0633\u0627\u062E\u062A\u0646\u0650 \u0631\u0645\u067E\u0650 \u0628\u0631\u0646\u062F \u0627\u0632 \u06CC\u06A9 \u0631\u0646\u06AF (\u0631\u0648\u06CC Shade \u06F6\u06F0\u06F0 \u0645\u06CC\u200C\u0646\u0634\u06CC\u0646\u062F)\u060C \u0645\u062B\u0644 #2C4C5B").action(initCommand);
3982
+ '\u0631\u0627\u0647\u200C\u0627\u0646\u062F\u0627\u0632\u06CC \u062F\u06CC\u06AF \u062F\u0631 \u067E\u0631\u0648\u0698\u0647: dig.json\u060C \u062A\u0645 \u0648 \u062A\u0648\u06A9\u0646\u200C\u0647\u0627\u060C dir="rtl"\u060C DESIGN.md \u0648 \u0641\u0627\u06CC\u0644\u0650 \u062F\u0633\u062A\u0648\u0631\u0650 \u06A9\u0627\u0631\u06CC\u0650 \u0627\u06CC\u062C\u0646\u062A'
3983
+ ).option("--yes", "\u067E\u0627\u0633\u062E \u067E\u06CC\u0634\u200C\u0641\u0631\u0636 \u0628\u0647 \u0647\u0645\u0647\u0654 \u0633\u0648\u0627\u0644\u0627\u062A", false).option("--brand <hex>", "\u0633\u0627\u062E\u062A\u0646\u0650 \u0631\u0645\u067E\u0650 \u0628\u0631\u0646\u062F \u0627\u0632 \u06CC\u06A9 \u0631\u0646\u06AF (\u0631\u0648\u06CC Shade \u06F6\u06F0\u06F0 \u0645\u06CC\u200C\u0646\u0634\u06CC\u0646\u062F)\u060C \u0645\u062B\u0644 #2C4C5B").option("--font <font>", "\u0641\u0648\u0646\u062A: iransans\u060C yekan \u06CC\u0627 vazirmatn").option("--agent <agent>", "\u0627\u06CC\u062C\u0646\u062A: claude-code\u060C codex\u060C cursor\u060C vscode \u06CC\u0627 gemini (\u0646\u0627\u0645\u0650 \u0641\u0627\u06CC\u0644\u0650 \u062F\u0633\u062A\u0648\u0631\u0650 \u06A9\u0627\u0631\u06CC \u0631\u0627 \u062A\u0639\u06CC\u06CC\u0646 \u0645\u06CC\u200C\u06A9\u0646\u062F)").action(initCommand);
3984
+ 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);
3641
3985
  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");
3642
3986
  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);
3643
3987
  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);
3988
+ program.command("mcp").description("\u0633\u0631\u0648\u0631 MCP \u062F\u06CC\u06AF \u0628\u0647 \u0634\u06A9\u0644 \u0633\u0631\u0648\u0631 \u0645\u062D\u0644\u06CC stdio\u060C \u0628\u0631\u0627\u06CC \u06A9\u0644\u0627\u06CC\u0646\u062A\u200C\u0647\u0627\u06CC\u06CC \u06A9\u0647 \u0622\u062F\u0631\u0633 HTTP \u0646\u0645\u06CC\u200C\u06AF\u06CC\u0631\u0646\u062F").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)").action(mcpCommand);
3644
3989
  program.command("add").description("\u0627\u0641\u0632\u0648\u062F\u0646 \u06CC\u06A9 \u06CC\u0627 \u0686\u0646\u062F \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u0627\u0632 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u062F\u06CC\u06AF").argument("[components...]", "\u0646\u0627\u0645 \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A\u200C\u0647\u0627 (\u0645\u062B\u0644 button) \u06CC\u0627 \u0622\u062F\u0631\u0633 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC\u061B \u0628\u062F\u0648\u0646 \u0622\u0631\u06AF\u0648\u0645\u0627\u0646 \u0641\u0647\u0631\u0633\u062A \u0631\u0627 \u0646\u0634\u0627\u0646 \u0645\u06CC\u200C\u062F\u0647\u062F").option("--overwrite", "\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u0645\u0648\u062C\u0648\u062F", false).option("-y, --yes", "\u0628\u062F\u0648\u0646 \u067E\u0631\u0633\u0634\u060C \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u0645\u0648\u062C\u0648\u062F \u0631\u0627 \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u06A9\u0646", false).option("--dry-run", "\u0641\u0642\u0637 \u0646\u0634\u0627\u0646 \u0628\u062F\u0647 \u0686\u0647 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC\u06CC \u0646\u0648\u0634\u062A\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F\u060C \u0686\u06CC\u0632\u06CC \u0646\u0646\u0648\u06CC\u0633", false).option("--no-install", "\u0641\u0642\u0637 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627 \u0631\u0627 \u0628\u0646\u0648\u06CC\u0633\u060C \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627 \u0631\u0627 \u0646\u0635\u0628 \u0646\u06A9\u0646").action(addCommand);
3645
3990
  program.command("list").description("\u0641\u0647\u0631\u0633\u062A \u0622\u06CC\u062A\u0645\u200C\u0647\u0627\u06CC \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u062F\u06CC\u06AF").option("--type <type>", "\u0641\u06CC\u0644\u062A\u0631 \u0646\u0648\u0639: ui\u060C lib \u06CC\u0627 theme").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(listCommand);
3646
3991
  program.command("search").description("\u062C\u0633\u062A\u062C\u0648\u06CC \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u062F\u0631 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC\u060C \u0628\u0627 \u0646\u0627\u0645 \u0641\u0627\u0631\u0633\u06CC \u06CC\u0627 \u0627\u0646\u06AF\u0644\u06CC\u0633\u06CC").argument("<query>", "\u0639\u0628\u0627\u0631\u062A \u062C\u0633\u062A\u062C\u0648").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(searchCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "digdesign",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI رسمی برای نصب کامپوننت‌های دیزاین سیستم دیگ",
5
5
  "type": "module",
6
6
  "license": "MIT",