create-fornix 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,9 +8,9 @@ import { defineCommand as defineCommand8 } from "citty";
8
8
 
9
9
  // src/cli/commands/create.ts
10
10
  import { defineCommand } from "citty";
11
- import { resolve, basename as basename2 } from "path";
12
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
13
- import { join as join4 } from "path";
11
+ import { resolve as resolve2, basename as basename2 } from "path";
12
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
13
+ import { join as join6 } from "path";
14
14
  import * as p2 from "@clack/prompts";
15
15
  import pc3 from "picocolors";
16
16
 
@@ -1768,7 +1768,6 @@ export function getStaticPaths() { return [{ params: { slug: '1' } }]; }
1768
1768
  "cta-newsletter": { "cta-newsletter.astro": "<section>CTA</section>\n" },
1769
1769
  "header-sticky": { "header-sticky.astro": "<header>Sticky</header>\n" }
1770
1770
  };
1771
- var FIXTURE_DEFAULT_CONTENT = {};
1772
1771
  function loadAllPalettes() {
1773
1772
  if (BUILTIN_PALETTES.length > 0) {
1774
1773
  return [...BUILTIN_PALETTES];
@@ -1802,6 +1801,202 @@ function findRegistryPalettesDir() {
1802
1801
  }
1803
1802
  }
1804
1803
 
1804
+ // src/registry/registry-fetcher.ts
1805
+ import { existsSync, readFileSync as readFileSync2, mkdirSync, writeFileSync, statSync } from "fs";
1806
+ import { join as join2 } from "path";
1807
+ import { homedir } from "os";
1808
+ var DEFAULT_CONFIG = {
1809
+ registryUrl: process.env.FORNIX_REGISTRY_URL || "https://raw.githubusercontent.com/kamsqe/fornix/main/packages/fornix-registry/registry.json",
1810
+ cacheDir: join2(homedir(), ".cache", "fornix"),
1811
+ force: process.env.FORNIX_NO_CACHE === "true",
1812
+ maxCacheAge: 24 * 60 * 60 * 1e3
1813
+ // 24 hours
1814
+ };
1815
+ async function fetchRegistryIndex(config = {}) {
1816
+ if (process.env.FORNIX_E2E_MOCK === "true") {
1817
+ return ok({ blocks: FIXTURE_MANIFESTS, palettes: BUILTIN_PALETTES });
1818
+ }
1819
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1820
+ const cacheFile = join2(cfg.cacheDir, "registry.json");
1821
+ const mapDataToIndex = (data) => {
1822
+ const blocksArray = Array.isArray(data.blocks) ? data.blocks : [];
1823
+ const blocksRecord = {};
1824
+ for (const block of blocksArray) {
1825
+ if (block.name) {
1826
+ blocksRecord[block.name] = block;
1827
+ }
1828
+ }
1829
+ return {
1830
+ blocks: blocksRecord,
1831
+ palettes: Array.isArray(data.palettes) ? data.palettes : []
1832
+ };
1833
+ };
1834
+ if (!cfg.force && isCacheValid(cacheFile, cfg.maxCacheAge)) {
1835
+ const cached = loadFromCache(cacheFile);
1836
+ if (cached) return ok(mapDataToIndex(cached));
1837
+ }
1838
+ try {
1839
+ const response = await fetch(cfg.registryUrl);
1840
+ if (!response.ok) {
1841
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1842
+ }
1843
+ const data = await response.json();
1844
+ mkdirSync(cfg.cacheDir, { recursive: true });
1845
+ writeFileSync(cacheFile, JSON.stringify(data, null, 2));
1846
+ return ok(mapDataToIndex(data));
1847
+ } catch (error) {
1848
+ const cached = loadFromCache(cacheFile);
1849
+ if (cached) {
1850
+ return ok(mapDataToIndex(cached));
1851
+ }
1852
+ const message = error instanceof Error ? error.message : String(error);
1853
+ return err({
1854
+ kind: "RegistryFetchError",
1855
+ message: `Failed to fetch registry from ${cfg.registryUrl}: ${message}`
1856
+ });
1857
+ }
1858
+ }
1859
+ function isCacheValid(cachePath, maxAge) {
1860
+ if (!existsSync(cachePath)) {
1861
+ return false;
1862
+ }
1863
+ try {
1864
+ const stat = statSync(cachePath);
1865
+ const age = Date.now() - stat.mtimeMs;
1866
+ return age < maxAge;
1867
+ } catch {
1868
+ return false;
1869
+ }
1870
+ }
1871
+ function loadFromCache(cachePath) {
1872
+ try {
1873
+ if (!existsSync(cachePath)) return null;
1874
+ const raw = readFileSync2(cachePath, "utf-8");
1875
+ return JSON.parse(raw);
1876
+ } catch {
1877
+ return null;
1878
+ }
1879
+ }
1880
+
1881
+ // src/registry/block-fetcher.ts
1882
+ import { downloadTemplate } from "giget";
1883
+ import {
1884
+ existsSync as existsSync2,
1885
+ readFileSync as readFileSync3,
1886
+ readdirSync as readdirSync2,
1887
+ mkdirSync as mkdirSync2,
1888
+ statSync as statSync2
1889
+ } from "fs";
1890
+ import { join as join3 } from "path";
1891
+ import { homedir as homedir2 } from "os";
1892
+ var DEFAULT_CONFIG2 = {
1893
+ repo: "kamsqe/fornix",
1894
+ ref: "main",
1895
+ cacheDir: join3(homedir2(), ".cache", "fornix", "blocks"),
1896
+ blocksPrefix: "packages/fornix-blocks/blocks",
1897
+ force: process.env.FORNIX_NO_CACHE === "true",
1898
+ maxCacheAge: 24 * 60 * 60 * 1e3
1899
+ // 24 hours
1900
+ };
1901
+ async function fetchBlock(blockName, config = {}) {
1902
+ if (process.env.FORNIX_E2E_MOCK === "true") {
1903
+ const manifest2 = FIXTURE_MANIFESTS[blockName];
1904
+ const files = FIXTURE_BLOCK_SOURCES[blockName];
1905
+ if (!manifest2 || !files) {
1906
+ return err({
1907
+ kind: "FetchError",
1908
+ blockName,
1909
+ message: `Mock block not found: ${blockName}`
1910
+ });
1911
+ }
1912
+ return ok({ manifest: manifest2, files, fromCache: true });
1913
+ }
1914
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
1915
+ const blockCacheDir = join3(cfg.cacheDir, blockName);
1916
+ if (!cfg.force && isCacheValid2(blockCacheDir, cfg.maxCacheAge)) {
1917
+ return loadFromCache2(blockName, blockCacheDir);
1918
+ }
1919
+ try {
1920
+ const source = `gh:${cfg.repo}/${cfg.blocksPrefix}/${blockName}#${cfg.ref}`;
1921
+ mkdirSync2(cfg.cacheDir, { recursive: true });
1922
+ await downloadTemplate(source, {
1923
+ dir: blockCacheDir,
1924
+ force: true
1925
+ // overwrite cache
1926
+ });
1927
+ return loadFromCache2(blockName, blockCacheDir);
1928
+ } catch (error) {
1929
+ if (existsSync2(join3(blockCacheDir, "block.json"))) {
1930
+ return loadFromCache2(blockName, blockCacheDir);
1931
+ }
1932
+ const message = error instanceof Error ? error.message : String(error);
1933
+ return err({
1934
+ kind: "FetchError",
1935
+ blockName,
1936
+ message: `Failed to fetch block '${blockName}': ${message}`
1937
+ });
1938
+ }
1939
+ }
1940
+ async function fetchBlocks(blockNames, config = {}) {
1941
+ return Promise.all(blockNames.map((name) => fetchBlock(name, config)));
1942
+ }
1943
+ function isCacheValid2(blockCacheDir, maxAge) {
1944
+ const manifestPath = join3(blockCacheDir, "block.json");
1945
+ if (!existsSync2(manifestPath)) {
1946
+ return false;
1947
+ }
1948
+ try {
1949
+ const stat = statSync2(manifestPath);
1950
+ const age = Date.now() - stat.mtimeMs;
1951
+ return age < maxAge;
1952
+ } catch {
1953
+ return false;
1954
+ }
1955
+ }
1956
+ function loadFromCache2(blockName, blockCacheDir) {
1957
+ try {
1958
+ const manifestPath = join3(blockCacheDir, "block.json");
1959
+ if (!existsSync2(manifestPath)) {
1960
+ return err({
1961
+ kind: "FetchError",
1962
+ blockName,
1963
+ message: `Cache miss: no block.json found in ${blockCacheDir}`
1964
+ });
1965
+ }
1966
+ const raw = readFileSync3(manifestPath, "utf-8");
1967
+ const parsed = JSON.parse(raw);
1968
+ const result = BlockManifestSchema.safeParse(parsed);
1969
+ if (!result.success) {
1970
+ const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
1971
+ return err({
1972
+ kind: "FetchError",
1973
+ blockName,
1974
+ message: `Invalid manifest in cache: ${issues}`
1975
+ });
1976
+ }
1977
+ const files = {};
1978
+ const entries = readdirSync2(blockCacheDir);
1979
+ for (const entry of entries) {
1980
+ const fullPath = join3(blockCacheDir, entry);
1981
+ if (statSync2(fullPath).isFile() && entry !== "block.json") {
1982
+ files[entry] = readFileSync3(fullPath, "utf-8");
1983
+ }
1984
+ }
1985
+ return ok({
1986
+ manifest: result.data,
1987
+ files,
1988
+ fromCache: true
1989
+ });
1990
+ } catch (error) {
1991
+ const message = error instanceof Error ? error.message : String(error);
1992
+ return err({
1993
+ kind: "FetchError",
1994
+ blockName,
1995
+ message: `Failed to load block from cache: ${message}`
1996
+ });
1997
+ }
1998
+ }
1999
+
1805
2000
  // src/prompts/manual-flow.ts
1806
2001
  import * as p from "@clack/prompts";
1807
2002
  import pc from "picocolors";
@@ -1992,8 +2187,8 @@ function buildSummary(config, blockNames, palette) {
1992
2187
 
1993
2188
  // src/scaffold/post-scaffold.ts
1994
2189
  import { execSync } from "child_process";
1995
- import { writeFileSync } from "fs";
1996
- import { join as join2, basename } from "path";
2190
+ import { writeFileSync as writeFileSync2 } from "fs";
2191
+ import { join as join4, basename } from "path";
1997
2192
  import pc2 from "picocolors";
1998
2193
  function runPostScaffold(input, callbacks) {
1999
2194
  const log = callbacks?.onLog ?? ((msg) => console.log(msg));
@@ -2159,7 +2354,7 @@ function generateClaudeMd(projectDir, config, blockNames) {
2159
2354
  lines.push("```");
2160
2355
  lines.push("");
2161
2356
  const content = lines.join("\n");
2162
- writeFileSync(join2(projectDir, "CLAUDE.md"), content, "utf-8");
2357
+ writeFileSync2(join4(projectDir, "CLAUDE.md"), content, "utf-8");
2163
2358
  }
2164
2359
 
2165
2360
  // src/ai/prompt-builder.ts
@@ -2187,7 +2382,8 @@ Your job:
2187
2382
 
2188
2383
  You MUST only select blocks that exist in the catalog below. Never invent block names.`;
2189
2384
  }
2190
- function buildBlocksCatalog(blocks) {
2385
+ function buildBlocksCatalog(blocksRecord) {
2386
+ const blocks = Object.values(blocksRecord);
2191
2387
  const lines = ["# Available Blocks\n"];
2192
2388
  const grouped = /* @__PURE__ */ new Map();
2193
2389
  for (const block of blocks) {
@@ -2267,7 +2463,8 @@ function buildPalettesCatalog(palettes) {
2267
2463
  }
2268
2464
  return lines.join("\n");
2269
2465
  }
2270
- function buildConstraintRules(blocks) {
2466
+ function buildConstraintRules(blocksRecord) {
2467
+ const blocks = Object.values(blocksRecord);
2271
2468
  const lines = ["# Constraint Rules\n"];
2272
2469
  lines.push("## Render Modes");
2273
2470
  lines.push("- `static` \u2014 Pre-rendered HTML, no server. Best for blogs, docs, landing pages.");
@@ -2993,8 +3190,8 @@ function createCloudflareProvider(opts = {}) {
2993
3190
  }
2994
3191
 
2995
3192
  // src/ai/providers/mock-provider.ts
2996
- import { readFileSync as readFileSync2, existsSync } from "fs";
2997
- import { join as join3, dirname as dirname2 } from "path";
3193
+ import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
3194
+ import { join as join5, dirname as dirname2 } from "path";
2998
3195
  import { fileURLToPath as fileURLToPath2 } from "url";
2999
3196
  var FIXTURE_ENTRIES = [
3000
3197
  {
@@ -3034,9 +3231,9 @@ function createMockProvider() {
3034
3231
  });
3035
3232
  }
3036
3233
  const fixtureDir = resolveFixtureDir();
3037
- const filePath = join3(fixtureDir, match.filename);
3234
+ const filePath = join5(fixtureDir, match.filename);
3038
3235
  try {
3039
- const raw = readFileSync2(filePath, "utf-8");
3236
+ const raw = readFileSync4(filePath, "utf-8");
3040
3237
  const parsed = JSON.parse(raw);
3041
3238
  const validated = IntentSchema.parse(parsed);
3042
3239
  return ok(validated);
@@ -3053,10 +3250,10 @@ function createMockProvider() {
3053
3250
  }
3054
3251
  function resolveFixtureDir() {
3055
3252
  const thisDir = dirname2(fileURLToPath2(import.meta.url));
3056
- const fromSource = join3(thisDir, "..", "..", "..", "tests", "fixtures", "ai-responses");
3057
- if (existsSync(fromSource)) return fromSource;
3058
- const fromDist = join3(thisDir, "..", "tests", "fixtures", "ai-responses");
3059
- if (existsSync(fromDist)) return fromDist;
3253
+ const fromSource = join5(thisDir, "..", "..", "..", "tests", "fixtures", "ai-responses");
3254
+ if (existsSync3(fromSource)) return fromSource;
3255
+ const fromDist = join5(thisDir, "..", "tests", "fixtures", "ai-responses");
3256
+ if (existsSync3(fromDist)) return fromDist;
3060
3257
  return fromSource;
3061
3258
  }
3062
3259
 
@@ -3363,30 +3560,37 @@ var createCommand = defineCommand({
3363
3560
  }
3364
3561
  },
3365
3562
  async run({ args: args2 }) {
3366
- const allPalettes = loadAllPalettes();
3563
+ const registryResult = await fetchRegistryIndex();
3564
+ if (!isOk(registryResult)) {
3565
+ console.error(pc3.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
3566
+ process.exitCode = 1;
3567
+ return;
3568
+ }
3569
+ const manifests = registryResult.value.blocks;
3570
+ const allPalettes = registryResult.value.palettes.length > 0 ? registryResult.value.palettes : loadAllPalettes();
3367
3571
  const hasExplicitFlags = !!(args2.render || args2.deploy || args2.blocks || args2.database || args2.css || args2.locales || args2.palette || args2.recipe);
3368
3572
  if (args2.manual && !args2.yes) {
3369
- const defaultProjectName = args2.dir ? basename2(resolve(args2.dir)) : "my-project";
3573
+ const defaultProjectName = args2.dir ? basename2(resolve2(args2.dir)) : "my-project";
3370
3574
  const config = await runManualFlow({
3371
3575
  defaultProjectName,
3372
- manifests: FIXTURE_MANIFESTS,
3576
+ manifests,
3373
3577
  allPalettes
3374
3578
  });
3375
3579
  if (!config) {
3376
3580
  process.exitCode = 0;
3377
3581
  return;
3378
3582
  }
3379
- const projectDir = args2.dir ? resolve(args2.dir) : resolve(config.projectDir);
3583
+ const projectDir = args2.dir ? resolve2(args2.dir) : resolve2(config.projectDir);
3380
3584
  const finalConfig = { ...config, projectDir };
3381
- return runScaffold(finalConfig, allPalettes, args2["dry-run"] ?? false, args2.verbose ?? false, !(args2.install ?? true), !(args2.git ?? true));
3585
+ return runScaffold(finalConfig, manifests, allPalettes, args2["dry-run"] ?? false, args2.verbose ?? false, !(args2.install ?? true), !(args2.git ?? true));
3382
3586
  }
3383
3587
  if (args2.manual || hasExplicitFlags) {
3384
- return runFlagDrivenMode(args2, allPalettes);
3588
+ return runFlagDrivenMode(args2, manifests, allPalettes);
3385
3589
  }
3386
- return runAIMode(args2, allPalettes);
3590
+ return runAIMode(args2, manifests, allPalettes);
3387
3591
  }
3388
3592
  });
3389
- async function runAIMode(args2, allPalettes) {
3593
+ async function runAIMode(args2, manifests, allPalettes) {
3390
3594
  const providerName = parseProviderName(args2.provider);
3391
3595
  if (args2.provider && !providerName) {
3392
3596
  console.error(pc3.red(`\u2716 Unknown provider: ${String(args2.provider)}`));
@@ -3410,10 +3614,10 @@ async function runAIMode(args2, allPalettes) {
3410
3614
  return;
3411
3615
  }
3412
3616
  const registry = {
3413
- blocks: Object.values(FIXTURE_MANIFESTS),
3617
+ blocks: Object.values(manifests),
3414
3618
  palettes: [...allPalettes]
3415
3619
  };
3416
- const projectDir = resolve(String(args2.dir ?? "."));
3620
+ const projectDir = resolve2(String(args2.dir ?? "."));
3417
3621
  const projectName = basename2(projectDir);
3418
3622
  const nameResult = validateProjectName(projectName);
3419
3623
  if (!nameResult.ok) {
@@ -3448,6 +3652,7 @@ async function runAIMode(args2, allPalettes) {
3448
3652
  }
3449
3653
  return runScaffold(
3450
3654
  config,
3655
+ manifests,
3451
3656
  allPalettes,
3452
3657
  (args2["dry-run"] ?? false) === true,
3453
3658
  (args2.verbose ?? false) === true,
@@ -3455,8 +3660,8 @@ async function runAIMode(args2, allPalettes) {
3455
3660
  !((args2.git ?? true) === true)
3456
3661
  );
3457
3662
  }
3458
- function runFlagDrivenMode(args2, allPalettes) {
3459
- const projectDir = resolve(String(args2.dir ?? "."));
3663
+ function runFlagDrivenMode(args2, manifests, allPalettes) {
3664
+ const projectDir = resolve2(String(args2.dir ?? "."));
3460
3665
  const projectName = basename2(projectDir);
3461
3666
  const nameResult = validateProjectName(projectName);
3462
3667
  if (!nameResult.ok) {
@@ -3539,6 +3744,7 @@ function runFlagDrivenMode(args2, allPalettes) {
3539
3744
  };
3540
3745
  return runScaffold(
3541
3746
  config,
3747
+ manifests,
3542
3748
  allPalettes,
3543
3749
  (args2["dry-run"] ?? false) === true,
3544
3750
  (args2.verbose ?? false) === true,
@@ -3546,12 +3752,36 @@ function runFlagDrivenMode(args2, allPalettes) {
3546
3752
  !((args2.git ?? true) === true)
3547
3753
  );
3548
3754
  }
3549
- function runScaffold(config, allPalettes, dryRun, verbose, skipInstall, skipGit) {
3755
+ async function runScaffold(config, manifests, allPalettes, dryRun, verbose, skipInstall, skipGit) {
3756
+ const spinner2 = p2.spinner();
3757
+ spinner2.start("Fetching blocks from registry...");
3758
+ const blockNames = config.blocks.map((b) => b.name);
3759
+ const blockResults = await fetchBlocks(blockNames);
3760
+ const blockSources = {};
3761
+ const blockDefaultContent = {};
3762
+ for (const result2 of blockResults) {
3763
+ if (!isOk(result2)) {
3764
+ spinner2.stop(`Failed to fetch block '${result2.error.blockName}'`, 1);
3765
+ console.error(pc3.red(`\u2716 ${result2.error.message}`));
3766
+ process.exitCode = 1;
3767
+ return;
3768
+ }
3769
+ const { manifest: manifest2, files: files2 } = result2.value;
3770
+ blockSources[manifest2.name] = files2;
3771
+ try {
3772
+ if (files2["default-content.json"]) {
3773
+ blockDefaultContent[manifest2.name] = JSON.parse(files2["default-content.json"]);
3774
+ }
3775
+ } catch (e) {
3776
+ console.error(pc3.yellow(`\u26A0 Warning: Failed to parse default-content.json for block '${manifest2.name}'`));
3777
+ }
3778
+ }
3779
+ spinner2.stop("Blocks fetched successfully.");
3550
3780
  const input = {
3551
3781
  config,
3552
- manifests: FIXTURE_MANIFESTS,
3553
- blockSources: FIXTURE_BLOCK_SOURCES,
3554
- blockDefaultContent: FIXTURE_DEFAULT_CONTENT,
3782
+ manifests,
3783
+ blockSources: Object.freeze(blockSources),
3784
+ blockDefaultContent: Object.freeze(blockDefaultContent),
3555
3785
  allPalettes
3556
3786
  };
3557
3787
  const result = scaffold(input);
@@ -3585,10 +3815,10 @@ function runScaffold(config, allPalettes, dryRun, verbose, skipInstall, skipGit)
3585
3815
  const files = result.value.files;
3586
3816
  let filesWritten = 0;
3587
3817
  for (const [relativePath, content] of Object.entries(files)) {
3588
- const fullPath = join4(config.projectDir, relativePath);
3589
- const parentDir = join4(fullPath, "..");
3590
- mkdirSync2(parentDir, { recursive: true });
3591
- writeFileSync2(fullPath, content, "utf-8");
3818
+ const fullPath = join6(config.projectDir, relativePath);
3819
+ const parentDir = join6(fullPath, "..");
3820
+ mkdirSync4(parentDir, { recursive: true });
3821
+ writeFileSync3(fullPath, content, "utf-8");
3592
3822
  filesWritten++;
3593
3823
  if (verbose) {
3594
3824
  console.log(pc3.dim(` created ${relativePath}`));
@@ -3677,8 +3907,8 @@ function showNoProviderGuide() {
3677
3907
  // src/cli/commands/add.ts
3678
3908
  import { defineCommand as defineCommand2 } from "citty";
3679
3909
  import pc4 from "picocolors";
3680
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
3681
- import { join as join5, dirname as dirname3 } from "path";
3910
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync5 } from "fs";
3911
+ import { join as join7, dirname as dirname3 } from "path";
3682
3912
  var addCommand = defineCommand2({
3683
3913
  meta: {
3684
3914
  name: "add",
@@ -3706,28 +3936,36 @@ var addCommand = defineCommand2({
3706
3936
  default: false
3707
3937
  }
3708
3938
  },
3709
- run({ args: args2 }) {
3939
+ async run({ args: args2 }) {
3710
3940
  const typedArgs = args2;
3711
3941
  const cwd = process.cwd();
3712
- const manifestPath = join5(cwd, "fornix.json");
3713
- if (!existsSync2(manifestPath)) {
3942
+ const manifestPath = join7(cwd, "fornix.json");
3943
+ if (!existsSync4(manifestPath)) {
3714
3944
  console.error(
3715
3945
  pc4.red("\u2717 No fornix.json found. Are you in a Fornix project?")
3716
3946
  );
3717
3947
  process.exit(1);
3718
3948
  }
3719
- const manifestRaw = readFileSync3(manifestPath, "utf-8");
3949
+ const manifestRaw = readFileSync5(manifestPath, "utf-8");
3720
3950
  const manifest2 = JSON.parse(manifestRaw);
3951
+ const registryResult = await fetchRegistryIndex();
3952
+ if (!isOk(registryResult)) {
3953
+ console.error(pc4.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
3954
+ process.exitCode = 1;
3955
+ return;
3956
+ }
3957
+ const manifests = registryResult.value.blocks;
3721
3958
  const blockName = typedArgs.block;
3722
- const blockManifest = FIXTURE_MANIFESTS[blockName];
3959
+ const blockManifest = manifests[blockName];
3723
3960
  if (!blockManifest) {
3724
3961
  console.error(pc4.red(`\u2717 Block '${blockName}' not found in registry.`));
3725
3962
  console.error(
3726
3963
  pc4.dim(
3727
- ` Available: ${Object.keys(FIXTURE_MANIFESTS).join(", ")}`
3964
+ ` Available: ${Object.keys(manifests).join(", ")}`
3728
3965
  )
3729
3966
  );
3730
- process.exit(1);
3967
+ process.exitCode = 1;
3968
+ return;
3731
3969
  }
3732
3970
  const installedNames = new Set(manifest2.blocks.map((b) => b.name));
3733
3971
  if (installedNames.has(blockName)) {
@@ -3736,26 +3974,32 @@ var addCommand = defineCommand2({
3736
3974
  );
3737
3975
  return;
3738
3976
  }
3739
- const blocksToAdd = resolveDependencies2(blockName, installedNames);
3977
+ const blocksToAdd = resolveDependencies2(blockName, installedNames, manifests);
3740
3978
  for (const name of blocksToAdd) {
3741
- const m = FIXTURE_MANIFESTS[name];
3979
+ const m = manifests[name];
3742
3980
  if (m?.requiredMode && manifest2.renderMode !== m.requiredMode) {
3743
3981
  console.error(
3744
3982
  pc4.red(
3745
3983
  `\u2717 Block '${name}' requires '${m.requiredMode}' mode, but project uses '${manifest2.renderMode}'.`
3746
3984
  )
3747
3985
  );
3748
- process.exit(1);
3986
+ process.exitCode = 1;
3987
+ return;
3749
3988
  }
3750
3989
  }
3990
+ console.log(pc4.dim("Fetching blocks..."));
3991
+ const blockResults = await fetchBlocks(blocksToAdd);
3751
3992
  const filesToWrite = [];
3752
- for (const name of blocksToAdd) {
3753
- const bManifest = FIXTURE_MANIFESTS[name];
3754
- const sources = FIXTURE_BLOCK_SOURCES[name];
3755
- if (!bManifest || !sources) {
3756
- console.error(pc4.red(`\u2717 Source files not found for block '${name}'.`));
3757
- process.exit(1);
3993
+ for (let i = 0; i < blocksToAdd.length; i++) {
3994
+ const name = blocksToAdd[i];
3995
+ const result = blockResults[i];
3996
+ if (!result || !isOk(result)) {
3997
+ console.error(pc4.red(`\u2717 Failed to fetch files for block '${name}'.`));
3998
+ process.exitCode = 1;
3999
+ return;
3758
4000
  }
4001
+ const bManifest = result.value.manifest;
4002
+ const sources = result.value.files;
3759
4003
  for (const file of bManifest.files) {
3760
4004
  const content = sources[file.source];
3761
4005
  if (content === void 0) {
@@ -3764,10 +4008,11 @@ var addCommand = defineCommand2({
3764
4008
  `\u2717 Source file '${file.source}' not found for block '${name}'.`
3765
4009
  )
3766
4010
  );
3767
- process.exit(1);
4011
+ process.exitCode = 1;
4012
+ return;
3768
4013
  }
3769
4014
  filesToWrite.push({
3770
- path: join5(cwd, file.destination),
4015
+ path: join7(cwd, file.destination),
3771
4016
  content
3772
4017
  });
3773
4018
  }
@@ -3788,15 +4033,15 @@ var addCommand = defineCommand2({
3788
4033
  return;
3789
4034
  }
3790
4035
  for (const file of filesToWrite) {
3791
- mkdirSync3(dirname3(file.path), { recursive: true });
3792
- writeFileSync3(file.path, file.content);
4036
+ mkdirSync5(dirname3(file.path), { recursive: true });
4037
+ writeFileSync4(file.path, file.content);
3793
4038
  if (typedArgs.verbose) {
3794
4039
  console.log(` ${pc4.dim("\u2192")} ${file.path}`);
3795
4040
  }
3796
4041
  }
3797
4042
  const now = (/* @__PURE__ */ new Date()).toISOString();
3798
4043
  for (const name of blocksToAdd) {
3799
- const bManifest = FIXTURE_MANIFESTS[name];
4044
+ const bManifest = manifests[name];
3800
4045
  if (!bManifest) continue;
3801
4046
  manifest2.blocks.push({
3802
4047
  name,
@@ -3805,7 +4050,7 @@ var addCommand = defineCommand2({
3805
4050
  installedAt: now
3806
4051
  });
3807
4052
  }
3808
- writeFileSync3(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4053
+ writeFileSync4(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
3809
4054
  console.log();
3810
4055
  for (const name of blocksToAdd) {
3811
4056
  const isDep = name !== blockName;
@@ -3826,13 +4071,13 @@ var addCommand = defineCommand2({
3826
4071
  console.log();
3827
4072
  }
3828
4073
  });
3829
- function resolveDependencies2(blockName, installedNames) {
4074
+ function resolveDependencies2(blockName, installedNames, manifests) {
3830
4075
  const result = [];
3831
4076
  const visited = /* @__PURE__ */ new Set();
3832
4077
  function walk(name) {
3833
4078
  if (visited.has(name) || installedNames.has(name)) return;
3834
4079
  visited.add(name);
3835
- const manifest2 = FIXTURE_MANIFESTS[name];
4080
+ const manifest2 = manifests[name];
3836
4081
  if (!manifest2) return;
3837
4082
  for (const dep of manifest2.requires) {
3838
4083
  walk(dep);
@@ -3846,8 +4091,8 @@ function resolveDependencies2(blockName, installedNames) {
3846
4091
  // src/cli/commands/remove.ts
3847
4092
  import { defineCommand as defineCommand3 } from "citty";
3848
4093
  import pc5 from "picocolors";
3849
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync3, unlinkSync, readdirSync as readdirSync2, rmdirSync } from "fs";
3850
- import { join as join6, dirname as dirname4 } from "path";
4094
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync5, unlinkSync, readdirSync as readdirSync3, rmdirSync } from "fs";
4095
+ import { join as join8, dirname as dirname4 } from "path";
3851
4096
  var removeCommand = defineCommand3({
3852
4097
  meta: {
3853
4098
  name: "remove",
@@ -3875,18 +4120,25 @@ var removeCommand = defineCommand3({
3875
4120
  default: false
3876
4121
  }
3877
4122
  },
3878
- run({ args: args2 }) {
4123
+ async run({ args: args2 }) {
3879
4124
  const typedArgs = args2;
3880
4125
  const cwd = process.cwd();
3881
- const manifestPath = join6(cwd, "fornix.json");
3882
- if (!existsSync3(manifestPath)) {
4126
+ const manifestPath = join8(cwd, "fornix.json");
4127
+ if (!existsSync5(manifestPath)) {
3883
4128
  console.error(
3884
4129
  pc5.red("\u2717 No fornix.json found. Are you in a Fornix project?")
3885
4130
  );
3886
4131
  process.exit(1);
3887
4132
  }
3888
- const manifestRaw = readFileSync4(manifestPath, "utf-8");
4133
+ const manifestRaw = readFileSync6(manifestPath, "utf-8");
3889
4134
  const manifest2 = JSON.parse(manifestRaw);
4135
+ const registryResult = await fetchRegistryIndex();
4136
+ if (!isOk(registryResult)) {
4137
+ console.error(pc5.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
4138
+ process.exitCode = 1;
4139
+ return;
4140
+ }
4141
+ const manifests = registryResult.value.blocks;
3890
4142
  const blockName = typedArgs.block;
3891
4143
  const installedNames = new Set(manifest2.blocks.map((b) => b.name));
3892
4144
  if (!installedNames.has(blockName)) {
@@ -3895,7 +4147,7 @@ var removeCommand = defineCommand3({
3895
4147
  );
3896
4148
  return;
3897
4149
  }
3898
- const dependents = findDependents(blockName, installedNames);
4150
+ const dependents = findDependents(blockName, installedNames, manifests);
3899
4151
  if (dependents.length > 0 && !typedArgs.force) {
3900
4152
  console.log(
3901
4153
  pc5.yellow(
@@ -3907,12 +4159,12 @@ var removeCommand = defineCommand3({
3907
4159
  );
3908
4160
  return;
3909
4161
  }
3910
- const blockManifest = FIXTURE_MANIFESTS[blockName];
4162
+ const blockManifest = manifests[blockName];
3911
4163
  const filesToRemove = [];
3912
4164
  if (blockManifest) {
3913
4165
  for (const file of blockManifest.files) {
3914
- const filePath = join6(cwd, file.destination);
3915
- if (existsSync3(filePath)) {
4166
+ const filePath = join8(cwd, file.destination);
4167
+ if (existsSync5(filePath)) {
3916
4168
  filesToRemove.push(filePath);
3917
4169
  }
3918
4170
  }
@@ -3934,7 +4186,7 @@ var removeCommand = defineCommand3({
3934
4186
  tryRemoveEmptyDir(dirname4(filePath), cwd);
3935
4187
  }
3936
4188
  manifest2.blocks = manifest2.blocks.filter((b) => b.name !== blockName);
3937
- writeFileSync4(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4189
+ writeFileSync5(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
3938
4190
  console.log();
3939
4191
  console.log(` ${pc5.red("-")} ${pc5.bold(blockName)} removed`);
3940
4192
  if (dependents.length > 0) {
@@ -3953,10 +4205,10 @@ var removeCommand = defineCommand3({
3953
4205
  console.log();
3954
4206
  }
3955
4207
  });
3956
- function findDependents(blockName, installedNames) {
4208
+ function findDependents(blockName, installedNames, manifests) {
3957
4209
  const dependents = [];
3958
4210
  for (const name of installedNames) {
3959
- const manifest2 = FIXTURE_MANIFESTS[name];
4211
+ const manifest2 = manifests[name];
3960
4212
  if (manifest2 && manifest2.requires.includes(blockName)) {
3961
4213
  dependents.push(name);
3962
4214
  }
@@ -3966,7 +4218,7 @@ function findDependents(blockName, installedNames) {
3966
4218
  function tryRemoveEmptyDir(dirPath, rootPath) {
3967
4219
  if (dirPath === rootPath || !dirPath.startsWith(rootPath)) return;
3968
4220
  try {
3969
- const entries = readdirSync2(dirPath);
4221
+ const entries = readdirSync3(dirPath);
3970
4222
  if (entries.length === 0) {
3971
4223
  rmdirSync(dirPath);
3972
4224
  tryRemoveEmptyDir(dirname4(dirPath), rootPath);
@@ -4003,9 +4255,16 @@ var listCommand = defineCommand4({
4003
4255
  default: false
4004
4256
  }
4005
4257
  },
4006
- run({ args: args2 }) {
4258
+ async run({ args: args2 }) {
4007
4259
  const typedArgs = args2;
4008
- const blocks = getFilteredBlocks(typedArgs);
4260
+ const registryResult = await fetchRegistryIndex();
4261
+ if (!isOk(registryResult)) {
4262
+ console.error(pc6.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
4263
+ process.exitCode = 1;
4264
+ return;
4265
+ }
4266
+ const manifests = registryResult.value.blocks;
4267
+ const blocks = getFilteredBlocks(typedArgs, manifests);
4009
4268
  if (blocks.length === 0) {
4010
4269
  console.log(pc6.yellow("No blocks found matching your filters."));
4011
4270
  return;
@@ -4017,8 +4276,8 @@ var listCommand = defineCommand4({
4017
4276
  }
4018
4277
  }
4019
4278
  });
4020
- function getFilteredBlocks(args2) {
4021
- let blocks = Object.values(FIXTURE_MANIFESTS);
4279
+ function getFilteredBlocks(args2, manifests) {
4280
+ let blocks = Object.values(manifests);
4022
4281
  if (args2.type) {
4023
4282
  const filterType = args2.type.toLowerCase();
4024
4283
  blocks = blocks.filter((b) => b.type === filterType);
@@ -4099,8 +4358,8 @@ function printFormatted(blocks, verbose) {
4099
4358
  // src/cli/commands/status.ts
4100
4359
  import { defineCommand as defineCommand5 } from "citty";
4101
4360
  import pc7 from "picocolors";
4102
- import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
4103
- import { join as join7 } from "path";
4361
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
4362
+ import { join as join9 } from "path";
4104
4363
  var statusCommand = defineCommand5({
4105
4364
  meta: {
4106
4365
  name: "status",
@@ -4121,8 +4380,8 @@ var statusCommand = defineCommand5({
4121
4380
  run({ args: args2 }) {
4122
4381
  const typedArgs = args2;
4123
4382
  const cwd = process.cwd();
4124
- const manifestPath = join7(cwd, "fornix.json");
4125
- if (!existsSync4(manifestPath)) {
4383
+ const manifestPath = join9(cwd, "fornix.json");
4384
+ if (!existsSync6(manifestPath)) {
4126
4385
  console.error(
4127
4386
  pc7.red("\u2717 No fornix.json found in the current directory.")
4128
4387
  );
@@ -4135,7 +4394,7 @@ var statusCommand = defineCommand5({
4135
4394
  }
4136
4395
  let manifest2;
4137
4396
  try {
4138
- const raw = readFileSync5(manifestPath, "utf-8");
4397
+ const raw = readFileSync7(manifestPath, "utf-8");
4139
4398
  manifest2 = JSON.parse(raw);
4140
4399
  } catch {
4141
4400
  console.error(pc7.red("\u2717 Failed to parse fornix.json."));
@@ -4216,8 +4475,8 @@ function printStatus(manifest2, verbose) {
4216
4475
  // src/cli/commands/doctor.ts
4217
4476
  import { defineCommand as defineCommand6 } from "citty";
4218
4477
  import pc8 from "picocolors";
4219
- import { readFileSync as readFileSync6, existsSync as existsSync5 } from "fs";
4220
- import { join as join8 } from "path";
4478
+ import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
4479
+ import { join as join10 } from "path";
4221
4480
  var doctorCommand = defineCommand6({
4222
4481
  meta: {
4223
4482
  name: "doctor",
@@ -4230,9 +4489,9 @@ var doctorCommand = defineCommand6({
4230
4489
  default: false
4231
4490
  }
4232
4491
  },
4233
- run({ args: args2 }) {
4492
+ async run({ args: args2 }) {
4234
4493
  const cwd = process.cwd();
4235
- const manifestPath = join8(cwd, "fornix.json");
4494
+ const manifestPath = join10(cwd, "fornix.json");
4236
4495
  let hasErrors = false;
4237
4496
  const errors = [];
4238
4497
  function reportError(msg) {
@@ -4242,7 +4501,7 @@ var doctorCommand = defineCommand6({
4242
4501
  console.error(pc8.red(`\u2717 ${msg}`));
4243
4502
  }
4244
4503
  }
4245
- if (!existsSync5(manifestPath)) {
4504
+ if (!existsSync7(manifestPath)) {
4246
4505
  reportError("No fornix.json found in the current directory.");
4247
4506
  if (args2.json) {
4248
4507
  console.log(JSON.stringify({ healthy: false, errors }));
@@ -4251,7 +4510,7 @@ var doctorCommand = defineCommand6({
4251
4510
  }
4252
4511
  let manifest2;
4253
4512
  try {
4254
- const raw = readFileSync6(manifestPath, "utf-8");
4513
+ const raw = readFileSync8(manifestPath, "utf-8");
4255
4514
  manifest2 = JSON.parse(raw);
4256
4515
  } catch {
4257
4516
  reportError("Failed to parse fornix.json.");
@@ -4260,24 +4519,34 @@ var doctorCommand = defineCommand6({
4260
4519
  }
4261
4520
  process.exit(1);
4262
4521
  }
4522
+ const registryResult = await fetchRegistryIndex();
4523
+ if (!isOk(registryResult)) {
4524
+ reportError(`Failed to fetch block registry: ${registryResult.error.message}`);
4525
+ if (args2.json) {
4526
+ console.log(JSON.stringify({ healthy: false, errors }));
4527
+ }
4528
+ process.exitCode = 1;
4529
+ return;
4530
+ }
4531
+ const manifests = registryResult.value.blocks;
4263
4532
  const isMultiLocale = manifest2.locales && manifest2.locales.length >= 2;
4264
4533
  const locales = isMultiLocale ? manifest2.locales : [""];
4265
4534
  const installedBlocks = new Set(manifest2.blocks.map((b) => b.name));
4266
4535
  for (const block of manifest2.blocks) {
4267
- const bManifest = FIXTURE_MANIFESTS[block.name];
4536
+ const bManifest = manifests[block.name];
4268
4537
  if (bManifest) {
4269
4538
  for (const file of bManifest.files) {
4270
- const filePath = join8(cwd, file.destination);
4271
- if (!existsSync5(filePath)) {
4539
+ const filePath = join10(cwd, file.destination);
4540
+ if (!existsSync7(filePath)) {
4272
4541
  reportError(`Missing expected file for installed block '${block.name}': ${file.destination}`);
4273
4542
  }
4274
4543
  }
4275
4544
  }
4276
4545
  }
4277
- for (const [name, bManifest] of Object.entries(FIXTURE_MANIFESTS)) {
4546
+ for (const [name, bManifest] of Object.entries(manifests)) {
4278
4547
  if (!installedBlocks.has(name)) {
4279
4548
  const foundOrphaned = bManifest.files.some((file) => {
4280
- return existsSync5(join8(cwd, file.destination));
4549
+ return existsSync7(join10(cwd, file.destination));
4281
4550
  });
4282
4551
  if (foundOrphaned) {
4283
4552
  reportError(`Orphaned block files detected for '${name}'. The block is not in fornix.json.`);
@@ -4287,7 +4556,7 @@ var doctorCommand = defineCommand6({
4287
4556
  let requiresContentConfig = false;
4288
4557
  const missingContentFiles = [];
4289
4558
  for (const block of manifest2.blocks) {
4290
- const bManifest = FIXTURE_MANIFESTS[block.name];
4559
+ const bManifest = manifests[block.name];
4291
4560
  if (!bManifest) continue;
4292
4561
  const hasContentSlots = bManifest.ai?.contentSlots && Object.keys(bManifest.ai.contentSlots).length > 0;
4293
4562
  const hasCollections = bManifest.collections && bManifest.collections.length > 0;
@@ -4301,14 +4570,14 @@ var doctorCommand = defineCommand6({
4301
4570
  if (locale !== "") {
4302
4571
  pathFragment = `src/content/${locale}/${subdirectory}/${bManifest.name}.json`;
4303
4572
  }
4304
- if (!existsSync5(join8(cwd, pathFragment))) {
4573
+ if (!existsSync7(join10(cwd, pathFragment))) {
4305
4574
  missingContentFiles.push(pathFragment);
4306
4575
  }
4307
4576
  }
4308
4577
  }
4309
4578
  }
4310
4579
  if (requiresContentConfig) {
4311
- if (!existsSync5(join8(cwd, "src/content/config.ts"))) {
4580
+ if (!existsSync7(join10(cwd, "src/content/config.ts"))) {
4312
4581
  reportError("Missing expected file: src/content/config.ts");
4313
4582
  }
4314
4583
  }
@@ -4361,57 +4630,71 @@ import {
4361
4630
  } from "@modelcontextprotocol/sdk/types.js";
4362
4631
 
4363
4632
  // src/mcp/tools/list-blocks.ts
4364
- function listBlocks(input) {
4365
- let blocks = Object.values(FIXTURE_MANIFESTS);
4366
- if (input.type) {
4367
- const filterType = input.type.toLowerCase();
4368
- blocks = blocks.filter((block) => block.type === filterType);
4369
- }
4370
- if (input.category) {
4371
- const filterCategory = input.category.toLowerCase();
4372
- blocks = blocks.filter((block) => block.category === filterCategory);
4373
- }
4374
- if (input.search) {
4375
- const searchTerm = input.search.toLowerCase();
4376
- blocks = blocks.filter(
4377
- (block) => block.name.includes(searchTerm) || block.description.toLowerCase().includes(searchTerm) || block.tags.some((tag) => tag.includes(searchTerm))
4378
- );
4633
+ async function listBlocksHandler(args2) {
4634
+ const result = await fetchRegistryIndex();
4635
+ if (!isOk(result)) {
4636
+ throw new Error(`Failed to fetch block registry: ${result.error.message}`);
4637
+ }
4638
+ const manifests = result.value.blocks;
4639
+ const typedArgs = args2;
4640
+ let blocks = Object.values(manifests);
4641
+ if (typedArgs?.type) {
4642
+ blocks = blocks.filter((b) => b.type === typedArgs.type);
4379
4643
  }
4380
- const entries = blocks.map((block) => ({
4381
- name: block.name,
4382
- type: block.type,
4383
- category: block.category,
4384
- description: block.description,
4385
- tags: block.tags,
4386
- requiredMode: block.requiredMode,
4387
- requires: block.requires
4388
- })).sort((a, b) => a.name.localeCompare(b.name));
4389
- return ok(entries);
4644
+ if (typedArgs?.category) {
4645
+ blocks = blocks.filter((b) => b.category === typedArgs.category);
4646
+ }
4647
+ return {
4648
+ content: [
4649
+ {
4650
+ type: "text",
4651
+ text: JSON.stringify(
4652
+ blocks.map((b) => ({
4653
+ name: b.name,
4654
+ description: b.description,
4655
+ type: b.type,
4656
+ category: b.category,
4657
+ requiredMode: b.requiredMode,
4658
+ requires: b.requires,
4659
+ conflicts: b.conflicts,
4660
+ envVars: b.envVars
4661
+ })),
4662
+ null,
4663
+ 2
4664
+ )
4665
+ }
4666
+ ]
4667
+ };
4390
4668
  }
4391
4669
 
4392
4670
  // src/mcp/tools/add-block.ts
4393
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
4394
- import { join as join9, dirname as dirname5 } from "path";
4395
- function addBlock2(input) {
4671
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
4672
+ import { join as join11, dirname as dirname5 } from "path";
4673
+ async function addBlock2(input) {
4396
4674
  const { name, variant = "default", projectDirectory } = input;
4397
- const manifestPath = join9(projectDirectory, "fornix.json");
4398
- if (!existsSync6(manifestPath)) {
4675
+ const manifestPath = join11(projectDirectory, "fornix.json");
4676
+ if (!existsSync8(manifestPath)) {
4399
4677
  return err(
4400
4678
  new Error("No fornix.json found. Not a Fornix project directory.")
4401
4679
  );
4402
4680
  }
4403
4681
  let manifest2;
4404
4682
  try {
4405
- const raw = readFileSync7(manifestPath, "utf-8");
4683
+ const raw = readFileSync9(manifestPath, "utf-8");
4406
4684
  manifest2 = JSON.parse(raw);
4407
4685
  } catch {
4408
4686
  return err(new Error("Failed to parse fornix.json."));
4409
4687
  }
4410
- const blockManifest = FIXTURE_MANIFESTS[name];
4688
+ const registryResult = await fetchRegistryIndex();
4689
+ if (!isOk(registryResult)) {
4690
+ return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
4691
+ }
4692
+ const manifests = registryResult.value.blocks;
4693
+ const blockManifest = manifests[name];
4411
4694
  if (!blockManifest) {
4412
4695
  return err(
4413
4696
  new Error(
4414
- `Block '${name}' not found in registry. Available: ${Object.keys(FIXTURE_MANIFESTS).join(", ")}`
4697
+ `Block '${name}' not found in registry. Available: ${Object.keys(manifests).join(", ")}`
4415
4698
  )
4416
4699
  );
4417
4700
  }
@@ -4419,9 +4702,9 @@ function addBlock2(input) {
4419
4702
  if (installedNames.has(name)) {
4420
4703
  return ok({ addedBlocks: [], filesCreated: 0 });
4421
4704
  }
4422
- const blocksToAdd = resolveDependencies3(name, installedNames);
4705
+ const blocksToAdd = resolveDependencies3(name, installedNames, manifests);
4423
4706
  for (const blockName of blocksToAdd) {
4424
- const dependencyManifest = FIXTURE_MANIFESTS[blockName];
4707
+ const dependencyManifest = manifests[blockName];
4425
4708
  if (dependencyManifest?.requiredMode && manifest2.renderMode !== dependencyManifest.requiredMode) {
4426
4709
  return err(
4427
4710
  new Error(
@@ -4431,14 +4714,15 @@ function addBlock2(input) {
4431
4714
  }
4432
4715
  }
4433
4716
  let filesCreated = 0;
4434
- for (const blockName of blocksToAdd) {
4435
- const blockDef = FIXTURE_MANIFESTS[blockName];
4436
- const sources = FIXTURE_BLOCK_SOURCES[blockName];
4437
- if (!blockDef || !sources) {
4438
- return err(
4439
- new Error(`Source files not found for block '${blockName}'.`)
4440
- );
4441
- }
4717
+ const blockResults = await fetchBlocks(blocksToAdd);
4718
+ for (let i = 0; i < blocksToAdd.length; i++) {
4719
+ const blockName = blocksToAdd[i];
4720
+ const result = blockResults[i];
4721
+ if (!result || !isOk(result)) {
4722
+ return err(new Error(`Failed to fetch files for block '${blockName}'.`));
4723
+ }
4724
+ const blockDef = result.value.manifest;
4725
+ const sources = result.value.files;
4442
4726
  for (const file of blockDef.files) {
4443
4727
  const content = sources[file.source];
4444
4728
  if (content === void 0) {
@@ -4448,15 +4732,15 @@ function addBlock2(input) {
4448
4732
  )
4449
4733
  );
4450
4734
  }
4451
- const filePath = join9(projectDirectory, file.destination);
4452
- mkdirSync4(dirname5(filePath), { recursive: true });
4453
- writeFileSync5(filePath, content);
4735
+ const filePath = join11(projectDirectory, file.destination);
4736
+ mkdirSync6(dirname5(filePath), { recursive: true });
4737
+ writeFileSync6(filePath, content);
4454
4738
  filesCreated++;
4455
4739
  }
4456
4740
  }
4457
4741
  const now = (/* @__PURE__ */ new Date()).toISOString();
4458
4742
  for (const blockName of blocksToAdd) {
4459
- const blockDef = FIXTURE_MANIFESTS[blockName];
4743
+ const blockDef = manifests[blockName];
4460
4744
  if (!blockDef) continue;
4461
4745
  manifest2.blocks.push({
4462
4746
  name: blockName,
@@ -4465,16 +4749,16 @@ function addBlock2(input) {
4465
4749
  installedAt: now
4466
4750
  });
4467
4751
  }
4468
- writeFileSync5(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4752
+ writeFileSync6(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4469
4753
  return ok({ addedBlocks: blocksToAdd, filesCreated });
4470
4754
  }
4471
- function resolveDependencies3(blockName, installedNames) {
4755
+ function resolveDependencies3(blockName, installedNames, manifests) {
4472
4756
  const result = [];
4473
4757
  const visited = /* @__PURE__ */ new Set();
4474
4758
  function walk(currentName) {
4475
4759
  if (visited.has(currentName) || installedNames.has(currentName)) return;
4476
4760
  visited.add(currentName);
4477
- const manifest2 = FIXTURE_MANIFESTS[currentName];
4761
+ const manifest2 = manifests[currentName];
4478
4762
  if (!manifest2) return;
4479
4763
  for (const dependency of manifest2.requires) {
4480
4764
  walk(dependency);
@@ -4487,34 +4771,39 @@ function resolveDependencies3(blockName, installedNames) {
4487
4771
 
4488
4772
  // src/mcp/tools/remove-block.ts
4489
4773
  import {
4490
- readFileSync as readFileSync8,
4491
- writeFileSync as writeFileSync6,
4492
- existsSync as existsSync7,
4774
+ readFileSync as readFileSync10,
4775
+ writeFileSync as writeFileSync7,
4776
+ existsSync as existsSync9,
4493
4777
  unlinkSync as unlinkSync2,
4494
- readdirSync as readdirSync3,
4778
+ readdirSync as readdirSync4,
4495
4779
  rmdirSync as rmdirSync2
4496
4780
  } from "fs";
4497
- import { join as join10, dirname as dirname6 } from "path";
4498
- function removeBlock(input) {
4781
+ import { join as join12, dirname as dirname6 } from "path";
4782
+ async function removeBlock(input) {
4499
4783
  const { name, force = false, projectDirectory } = input;
4500
- const manifestPath = join10(projectDirectory, "fornix.json");
4501
- if (!existsSync7(manifestPath)) {
4784
+ const manifestPath = join12(projectDirectory, "fornix.json");
4785
+ if (!existsSync9(manifestPath)) {
4502
4786
  return err(
4503
4787
  new Error("No fornix.json found. Not a Fornix project directory.")
4504
4788
  );
4505
4789
  }
4506
4790
  let manifest2;
4507
4791
  try {
4508
- const raw = readFileSync8(manifestPath, "utf-8");
4792
+ const raw = readFileSync10(manifestPath, "utf-8");
4509
4793
  manifest2 = JSON.parse(raw);
4510
4794
  } catch {
4511
4795
  return err(new Error("Failed to parse fornix.json."));
4512
4796
  }
4797
+ const registryResult = await fetchRegistryIndex();
4798
+ if (!isOk(registryResult)) {
4799
+ return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
4800
+ }
4801
+ const manifests = registryResult.value.blocks;
4513
4802
  const installedNames = new Set(manifest2.blocks.map((block) => block.name));
4514
4803
  if (!installedNames.has(name)) {
4515
4804
  return err(new Error(`Block '${name}' is not installed.`));
4516
4805
  }
4517
- const dependents = findDependents2(name, installedNames);
4806
+ const dependents = findDependents2(name, installedNames, manifests);
4518
4807
  if (dependents.length > 0 && !force) {
4519
4808
  return err(
4520
4809
  new Error(
@@ -4522,12 +4811,12 @@ function removeBlock(input) {
4522
4811
  )
4523
4812
  );
4524
4813
  }
4525
- const blockManifest = FIXTURE_MANIFESTS[name];
4814
+ const blockManifest = manifests[name];
4526
4815
  const filesToRemove = [];
4527
4816
  if (blockManifest) {
4528
4817
  for (const file of blockManifest.files) {
4529
- const filePath = join10(projectDirectory, file.destination);
4530
- if (existsSync7(filePath)) {
4818
+ const filePath = join12(projectDirectory, file.destination);
4819
+ if (existsSync9(filePath)) {
4531
4820
  filesToRemove.push(filePath);
4532
4821
  }
4533
4822
  }
@@ -4537,17 +4826,17 @@ function removeBlock(input) {
4537
4826
  tryRemoveEmptyDirectory(dirname6(filePath), projectDirectory);
4538
4827
  }
4539
4828
  manifest2.blocks = manifest2.blocks.filter((block) => block.name !== name);
4540
- writeFileSync6(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4829
+ writeFileSync7(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4541
4830
  return ok({
4542
4831
  removedBlock: name,
4543
4832
  filesRemoved: filesToRemove.length,
4544
4833
  dependentsWarning: dependents
4545
4834
  });
4546
4835
  }
4547
- function findDependents2(blockName, installedNames) {
4836
+ function findDependents2(blockName, installedNames, manifests) {
4548
4837
  const dependents = [];
4549
4838
  for (const installedName of installedNames) {
4550
- const manifest2 = FIXTURE_MANIFESTS[installedName];
4839
+ const manifest2 = manifests[installedName];
4551
4840
  if (manifest2 && manifest2.requires.includes(blockName)) {
4552
4841
  dependents.push(installedName);
4553
4842
  }
@@ -4557,7 +4846,7 @@ function findDependents2(blockName, installedNames) {
4557
4846
  function tryRemoveEmptyDirectory(directoryPath, rootPath) {
4558
4847
  if (directoryPath === rootPath || !directoryPath.startsWith(rootPath)) return;
4559
4848
  try {
4560
- const entries = readdirSync3(directoryPath);
4849
+ const entries = readdirSync4(directoryPath);
4561
4850
  if (entries.length === 0) {
4562
4851
  rmdirSync2(directoryPath);
4563
4852
  tryRemoveEmptyDirectory(dirname6(directoryPath), rootPath);
@@ -4567,33 +4856,51 @@ function tryRemoveEmptyDirectory(directoryPath, rootPath) {
4567
4856
  }
4568
4857
 
4569
4858
  // src/mcp/tools/get-content-schema.ts
4570
- function getContentSchema(input) {
4571
- const { collection } = input;
4572
- const manifest2 = FIXTURE_MANIFESTS[collection];
4859
+ async function getContentSchemaHandler(args2) {
4860
+ const typedArgs = args2;
4861
+ if (!typedArgs?.block) {
4862
+ throw new Error("Missing required argument: block");
4863
+ }
4864
+ const result = await fetchRegistryIndex();
4865
+ if (!isOk(result)) {
4866
+ throw new Error(`Failed to fetch block registry: ${result.error.message}`);
4867
+ }
4868
+ const manifests = result.value.blocks;
4869
+ const manifest2 = manifests[typedArgs.block];
4573
4870
  if (!manifest2) {
4574
- return err(
4575
- new Error(`Collection '${collection}' not found in registry.`)
4576
- );
4871
+ throw new Error(`Block '${typedArgs.block}' not found in registry`);
4577
4872
  }
4578
- const contentSlots = manifest2.ai?.contentSlots;
4579
- if (!contentSlots || Object.keys(contentSlots).length === 0) {
4580
- return err(
4581
- new Error(
4582
- `Block '${collection}' does not define content slots.`
4583
- )
4584
- );
4873
+ const slots = manifest2.ai?.contentSlots ?? {};
4874
+ if (Object.keys(slots).length === 0) {
4875
+ return {
4876
+ content: [
4877
+ {
4878
+ type: "text",
4879
+ text: `Block '${typedArgs.block}' does not have any AI-configurable content slots.`
4880
+ }
4881
+ ]
4882
+ };
4585
4883
  }
4586
- return ok({
4587
- collection,
4588
- slots: contentSlots
4589
- });
4884
+ return {
4885
+ content: [
4886
+ {
4887
+ type: "text",
4888
+ text: JSON.stringify(slots, null, 2)
4889
+ }
4890
+ ]
4891
+ };
4590
4892
  }
4591
4893
 
4592
4894
  // src/mcp/tools/validate-content.ts
4593
4895
  import { z as z5 } from "zod";
4594
- function validateContent(input) {
4896
+ async function validateContent(input) {
4595
4897
  const { collection, data } = input;
4596
- const manifest2 = FIXTURE_MANIFESTS[collection];
4898
+ const registryResult = await fetchRegistryIndex();
4899
+ if (!isOk(registryResult)) {
4900
+ return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
4901
+ }
4902
+ const manifests = registryResult.value.blocks;
4903
+ const manifest2 = manifests[collection];
4597
4904
  if (!manifest2) {
4598
4905
  return err(
4599
4906
  new Error(
@@ -4641,17 +4948,17 @@ function zodTypeForSlot2(slotType) {
4641
4948
  }
4642
4949
 
4643
4950
  // src/mcp/tools/get-project-status.ts
4644
- import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
4645
- import { join as join11 } from "path";
4951
+ import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
4952
+ import { join as join13 } from "path";
4646
4953
  function getProjectStatus(input) {
4647
- const manifestPath = join11(input.projectDirectory, "fornix.json");
4648
- if (!existsSync8(manifestPath)) {
4954
+ const manifestPath = join13(input.projectDirectory, "fornix.json");
4955
+ if (!existsSync10(manifestPath)) {
4649
4956
  return err(
4650
4957
  new Error("No fornix.json found. Not a Fornix project directory.")
4651
4958
  );
4652
4959
  }
4653
4960
  try {
4654
- const raw = readFileSync9(manifestPath, "utf-8");
4961
+ const raw = readFileSync11(manifestPath, "utf-8");
4655
4962
  const manifest2 = JSON.parse(raw);
4656
4963
  const blocks = manifest2.blocks;
4657
4964
  return ok({
@@ -4673,8 +4980,8 @@ function getProjectStatus(input) {
4673
4980
  }
4674
4981
 
4675
4982
  // src/mcp/tools/scaffold-project.ts
4676
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
4677
- import { join as join12, basename as basename3 } from "path";
4983
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
4984
+ import { join as join14, basename as basename3 } from "path";
4678
4985
  var DEFAULT_COLORS2 = {
4679
4986
  primary: "#6366f1",
4680
4987
  secondary: "#818cf8",
@@ -4682,7 +4989,7 @@ var DEFAULT_COLORS2 = {
4682
4989
  background: "#0f172a",
4683
4990
  foreground: "#f8fafc"
4684
4991
  };
4685
- function scaffoldProject(input) {
4992
+ async function scaffoldProject(input) {
4686
4993
  const {
4687
4994
  projectDirectory,
4688
4995
  renderMode = "static",
@@ -4690,9 +4997,14 @@ function scaffoldProject(input) {
4690
4997
  blocks = [],
4691
4998
  locales = ["en"]
4692
4999
  } = input;
5000
+ const registryResult = await fetchRegistryIndex();
5001
+ if (!isOk(registryResult)) {
5002
+ return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
5003
+ }
5004
+ const manifests = registryResult.value.blocks;
5005
+ const allPalettes = registryResult.value.palettes.length > 0 ? registryResult.value.palettes : loadAllPalettes();
4693
5006
  const projectName = basename3(projectDirectory);
4694
5007
  const blockSelections = blocks.map((name) => ({ name, variant: "default" }));
4695
- const allPalettes = loadAllPalettes();
4696
5008
  const config = {
4697
5009
  projectName,
4698
5010
  projectDir: projectDirectory,
@@ -4710,11 +5022,27 @@ function scaffoldProject(input) {
4710
5022
  themeSwitcher: false,
4711
5023
  createdWith: "mcp"
4712
5024
  };
5025
+ const blockResults = await fetchBlocks([...blocks]);
5026
+ const blockSources = {};
5027
+ const blockDefaultContent = {};
5028
+ for (const result2 of blockResults) {
5029
+ if (!isOk(result2)) {
5030
+ return err(new Error(`Failed to fetch block '${result2.error.blockName}': ${result2.error.message}`));
5031
+ }
5032
+ const { manifest: manifest2, files: files2 } = result2.value;
5033
+ blockSources[manifest2.name] = files2;
5034
+ try {
5035
+ if (files2["default-content.json"]) {
5036
+ blockDefaultContent[manifest2.name] = JSON.parse(files2["default-content.json"]);
5037
+ }
5038
+ } catch {
5039
+ }
5040
+ }
4713
5041
  const scaffoldInput = {
4714
5042
  config,
4715
- manifests: FIXTURE_MANIFESTS,
4716
- blockSources: FIXTURE_BLOCK_SOURCES,
4717
- blockDefaultContent: FIXTURE_DEFAULT_CONTENT,
5043
+ manifests,
5044
+ blockSources,
5045
+ blockDefaultContent,
4718
5046
  allPalettes
4719
5047
  };
4720
5048
  const result = scaffold(scaffoldInput);
@@ -4724,10 +5052,10 @@ function scaffoldProject(input) {
4724
5052
  const files = result.value.files;
4725
5053
  let filesCreated = 0;
4726
5054
  for (const [relativePath, content] of Object.entries(files)) {
4727
- const fullPath = join12(projectDirectory, relativePath);
4728
- const parentDirectory = join12(fullPath, "..");
4729
- mkdirSync5(parentDirectory, { recursive: true });
4730
- writeFileSync7(fullPath, content, "utf-8");
5055
+ const fullPath = join14(projectDirectory, relativePath);
5056
+ const parentDirectory = join14(fullPath, "..");
5057
+ mkdirSync7(parentDirectory, { recursive: true });
5058
+ writeFileSync8(fullPath, content, "utf-8");
4731
5059
  filesCreated++;
4732
5060
  }
4733
5061
  return ok({
@@ -4924,7 +5252,11 @@ var FornixMCPServer = class {
4924
5252
  async (request) => {
4925
5253
  const uri = request.params.uri;
4926
5254
  if (uri === "fornix://registry") {
4927
- const blocks = Object.values(FIXTURE_MANIFESTS).map((manifest2) => ({
5255
+ const registryResult = await fetchRegistryIndex();
5256
+ if (!isOk(registryResult)) {
5257
+ throw new Error(`Failed to fetch block registry: ${registryResult.error.message}`);
5258
+ }
5259
+ const blocks = Object.values(registryResult.value.blocks).map((manifest2) => ({
4928
5260
  name: manifest2.name,
4929
5261
  type: manifest2.type,
4930
5262
  category: manifest2.category,
@@ -4967,16 +5299,19 @@ var FornixMCPServer = class {
4967
5299
  async executeTool(toolName, args2) {
4968
5300
  switch (toolName) {
4969
5301
  case "list_blocks": {
4970
- const result = listBlocks({
4971
- type: args2.type,
4972
- category: args2.category,
4973
- search: args2.search
4974
- });
4975
- if (!result.ok) return err(result.error);
4976
- return ok(JSON.stringify(result.value, null, 2));
5302
+ try {
5303
+ const response = await listBlocksHandler({
5304
+ type: args2.type,
5305
+ category: args2.category,
5306
+ search: args2.search
5307
+ });
5308
+ return ok(response.content[0].text);
5309
+ } catch (e) {
5310
+ return err(e instanceof Error ? e : new Error(String(e)));
5311
+ }
4977
5312
  }
4978
5313
  case "add_block": {
4979
- const result = addBlock2({
5314
+ const result = await addBlock2({
4980
5315
  name: args2.name,
4981
5316
  variant: args2.variant,
4982
5317
  projectDirectory: args2.projectDirectory
@@ -4985,7 +5320,7 @@ var FornixMCPServer = class {
4985
5320
  return ok(JSON.stringify(result.value, null, 2));
4986
5321
  }
4987
5322
  case "remove_block": {
4988
- const result = removeBlock({
5323
+ const result = await removeBlock({
4989
5324
  name: args2.name,
4990
5325
  force: args2.force,
4991
5326
  projectDirectory: args2.projectDirectory
@@ -4994,14 +5329,17 @@ var FornixMCPServer = class {
4994
5329
  return ok(JSON.stringify(result.value, null, 2));
4995
5330
  }
4996
5331
  case "get_content_schema": {
4997
- const result = getContentSchema({
4998
- collection: args2.collection
4999
- });
5000
- if (!result.ok) return err(result.error);
5001
- return ok(JSON.stringify(result.value, null, 2));
5332
+ try {
5333
+ const response = await getContentSchemaHandler({
5334
+ block: args2.collection
5335
+ });
5336
+ return ok(response.content[0].text);
5337
+ } catch (e) {
5338
+ return err(e instanceof Error ? e : new Error(String(e)));
5339
+ }
5002
5340
  }
5003
5341
  case "update_content": {
5004
- const validationResult = validateContent({
5342
+ const validationResult = await validateContent({
5005
5343
  collection: args2.collection,
5006
5344
  data: args2.data
5007
5345
  });
@@ -5023,7 +5361,7 @@ var FornixMCPServer = class {
5023
5361
  );
5024
5362
  }
5025
5363
  case "validate_content": {
5026
- const result = validateContent({
5364
+ const result = await validateContent({
5027
5365
  collection: args2.collection,
5028
5366
  data: args2.data
5029
5367
  });
@@ -5038,7 +5376,7 @@ var FornixMCPServer = class {
5038
5376
  return ok(JSON.stringify(result.value, null, 2));
5039
5377
  }
5040
5378
  case "scaffold_project": {
5041
- const result = scaffoldProject({
5379
+ const result = await scaffoldProject({
5042
5380
  description: args2.description,
5043
5381
  projectDirectory: args2.projectDirectory,
5044
5382
  renderMode: args2.renderMode,