@loomweaver/mcp 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.mjs +160 -130
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ is not inside this repo (a chat assistant, a remote client) can scaffold and val
10
10
  - `scaffold_weaver` · `scaffold_frame_plugin` · `scaffold_distribution` · `scaffold_auth_source` ·
11
11
  `scaffold_settings_store` · `scaffold_theme` · `scaffold_layout` — return a **file map**
12
12
  (`path -> content`); the client writes the files into its project.
13
- - `validate_manifest` · `validate_i18n` · `validate_catalog` — return structured `Finding[]`.
13
+ - `validate_manifest` · `validate_i18n` · `validate_catalog` · `validate_commands` — return structured `Finding[]`.
14
14
 
15
15
  ## Consume from npm (product repos)
16
16
 
package/dist/main.mjs CHANGED
@@ -31025,6 +31025,10 @@ function normalizeProjectRoot(value) {
31025
31025
  }
31026
31026
  return rooted.slice(0, end);
31027
31027
  }
31028
+ function joinProjectPath(projectRoot, path) {
31029
+ const root = normalizeProjectRoot(projectRoot);
31030
+ return root ? `${root}/${path}` : path;
31031
+ }
31028
31032
 
31029
31033
  // ../devkit/src/lib/amend/describe.ts
31030
31034
  function describeAmendment(amendment) {
@@ -31038,15 +31042,19 @@ function describeAmendment(amendment) {
31038
31042
  return `Add an @source entry for '${amendment.sourceRoot}' to the application's entry stylesheet, resolved from that stylesheet. Without it none of that code's utilities are emitted.`;
31039
31043
  }
31040
31044
  if (amendment.kind === "compose-plugin") {
31041
- const providers = (amendment.providers ?? []).map((provider) => provider.line.replace(/,$/, ""));
31042
- return `Register ${amendment.id} in the composition root: import { ${amendment.symbol} }, ${providers.map((line) => `${line}, `).join("")}provideTranslationNamespaces('${amendment.id}'), provideCapabilityGrants({ ${amendment.id}: [${amendment.capabilities.map((capability) => `'${capability}'`).join(
31043
- ", "
31044
- )}] }) and ...providePlugins(${amendment.symbol}). Without it none of its contributions appear.`;
31045
+ const providers = (amendment.providers ?? []).map((provider) => `${provider.line.replace(/,$/, "")}, `).join("");
31046
+ const capabilities = amendment.capabilities.map((capability) => `'${capability}'`).join(", ");
31047
+ const imports = (amendment.providers ?? []).flatMap((provider) => provider.from ?? []).map(
31048
+ (imported) => ` import { ${imported.symbols.join(", ")} } from '${imported.path}'.`
31049
+ ).join("");
31050
+ return `Register ${amendment.id} in the composition root: import { ${amendment.symbol} }, ${providers}provideTranslationNamespaces('${amendment.id}'), provideCapabilityGrants({ ${amendment.id}: [${capabilities}] }) and ...providePlugins(${amendment.symbol}).${imports} Without it none of its contributions appear.`;
31045
31051
  }
31046
31052
  return [
31047
31053
  ...amendment.styles.length > 0 ? [`name ${amendment.styles.join(", ")} in styles`] : [],
31048
31054
  ...amendment.assets.length > 0 ? [
31049
- `add assets for ${amendment.assets.map((asset) => asset.input).join(
31055
+ `add assets for ${amendment.assets.map(
31056
+ (asset) => asset.output ? `${asset.input} served under ${asset.output}` : asset.input
31057
+ ).join(
31050
31058
  ", "
31051
31059
  )} (the shell fetches its own strings at runtime, so without that glob every label in the chrome renders as its raw translation key)`
31052
31060
  ] : [],
@@ -31055,7 +31063,10 @@ function describeAmendment(amendment) {
31055
31063
  ] : [],
31056
31064
  ...amendment.inlineCritical === void 0 ? [] : [
31057
31065
  `set optimization.styles.inlineCritical to ${amendment.inlineCritical} in the production configuration (the generated content-security policy blocks the inline handler Angular's critical-CSS pass attaches, so a release build renders unstyled)`
31058
- ]
31066
+ ],
31067
+ ...amendment.initialBudget ? [
31068
+ `raise the initial bundle budget to ${amendment.initialBudget.warning} warning and ${amendment.initialBudget.error} error in the production configuration (a fresh workspace carries one sized for an empty application, and the workbench alone spends almost all of it, so a release build fails)`
31069
+ ] : []
31059
31070
  ].map((step, index) => `${index + 1}. ${step}`).join(" ");
31060
31071
  }
31061
31072
 
@@ -31447,7 +31458,7 @@ function pause(): Promise<void> {
31447
31458
  }
31448
31459
 
31449
31460
  // ../devkit/src/recipes/angular-weaver/agent-files.ts
31450
- var AG_UI_ADAPTER_VERSION = "0.9.1";
31461
+ var AG_UI_ADAPTER_VERSION = "0.9.3";
31451
31462
  var AG_UI_PROTOCOL_VERSION = "0.0.x";
31452
31463
  function connectionFile(w) {
31453
31464
  return `import { signal } from '@angular/core';
@@ -31814,109 +31825,8 @@ function toneHelper() {
31814
31825
  ].join("\n");
31815
31826
  }
31816
31827
 
31817
- // ../devkit/src/recipes/angular-weaver/recipe.ts
31818
- var DEFAULT_MENU_SLOT = "content/tab/context";
31828
+ // ../devkit/src/recipes/angular-weaver/weaver-plugin.ts
31819
31829
  var SURFACE_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 9h8M8 13h8M8 17h5"/></svg>';
31820
- function accessLiteral(spec) {
31821
- if (spec === "authenticated") return "{ authenticated: true }";
31822
- if (spec === "anonymous") return "{ authenticated: false }";
31823
- if (spec.startsWith("role:")) {
31824
- const role = spec.slice("role:".length);
31825
- if (!role)
31826
- throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
31827
- if (/['\\]/.test(role))
31828
- throw new Error(
31829
- `Access role "${role}" must not contain quotes or backslashes.`
31830
- );
31831
- return `{ anyRole: ['${role}'] }`;
31832
- }
31833
- throw new Error(
31834
- `Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
31835
- );
31836
- }
31837
- function resolveMenuSlot(menu) {
31838
- if (menu === true) {
31839
- return DEFAULT_MENU_SLOT;
31840
- }
31841
- return typeof menu === "string" && menu.length ? menu : void 0;
31842
- }
31843
- var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
31844
- "cmd",
31845
- "command",
31846
- "ctrl",
31847
- "control",
31848
- "meta"
31849
- ]);
31850
- function assertPlatformNeutralChord(shortcut) {
31851
- const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
31852
- const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
31853
- if (bound) {
31854
- throw new Error(
31855
- `Shortcut "${shortcut}" binds the platform-specific "${bound}" key. Use the neutral 'mod' token (e.g. 'mod+shift+k') \u2014 the host renders it as \u2318 on macOS and Ctrl elsewhere.`
31856
- );
31857
- }
31858
- }
31859
- function resolveFeatures(id, input) {
31860
- const menuSlot = resolveMenuSlot(input?.menu);
31861
- const barItem = Boolean(input?.barItem);
31862
- const hasShortcut = Boolean(input?.shortcut?.trim());
31863
- if (hasShortcut) {
31864
- assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
31865
- }
31866
- const instanceable = Boolean(input?.instanceable);
31867
- const container = Boolean(input?.container);
31868
- if (instanceable && container) {
31869
- throw new Error(
31870
- 'A surface cannot be both a container and instanceable: a container tab holds its own ":id" and is therefore routable, while named instances exist only for a docked, non-routable surface. Pick one.'
31871
- );
31872
- }
31873
- const agent = Boolean(input?.agent);
31874
- return {
31875
- command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
31876
- menuSlot,
31877
- settings: Boolean(input?.settings),
31878
- access: input?.access ? accessLiteral(input.access) : void 0,
31879
- shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
31880
- barItem,
31881
- about: Boolean(input?.about),
31882
- instanceable,
31883
- container,
31884
- agent,
31885
- spec: input?.spec !== false
31886
- };
31887
- }
31888
- function deriveCapabilities(features) {
31889
- const set2 = /* @__PURE__ */ new Set(["contributions", "navigation"]);
31890
- if (features.command) set2.add("ui");
31891
- if (features.about) {
31892
- set2.add("ui");
31893
- set2.add("host");
31894
- }
31895
- if (features.agent) {
31896
- set2.add("ui");
31897
- set2.add("automation");
31898
- }
31899
- return KNOWN_CAPABILITIES.filter((capability) => set2.has(capability));
31900
- }
31901
- function resolveWeaverInput(input) {
31902
- if (!isKebabId(input.id)) {
31903
- throw new Error(
31904
- `Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
31905
- );
31906
- }
31907
- const features = resolveFeatures(input.id, input.features);
31908
- const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
31909
- return {
31910
- id: input.id,
31911
- name: input.name?.trim() || toTitleCase(input.id),
31912
- className: toPascalCase(input.id),
31913
- propertyName: toCamelCase(input.id),
31914
- capabilities,
31915
- features,
31916
- prefix: input.prefix?.trim() || "lw",
31917
- importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
31918
- };
31919
- }
31920
31830
  function containerChildIds(w) {
31921
31831
  return [`${w.id}.canvas`, `${w.id}.details`];
31922
31832
  }
@@ -32053,7 +31963,7 @@ function settingsBlock(w) {
32053
31963
  " });"
32054
31964
  ].join("\n");
32055
31965
  }
32056
- function pluginFile(w) {
31966
+ function pluginImports(w) {
32057
31967
  const imports = ["import { Plugin } from '@loomweaver/plugin-sdk';"];
32058
31968
  if (w.features.container) {
32059
31969
  imports.push(
@@ -32077,6 +31987,9 @@ function pluginFile(w) {
32077
31987
  if (w.features.settings) {
32078
31988
  imports.unshift("import { signal } from '@angular/core';");
32079
31989
  }
31990
+ return imports;
31991
+ }
31992
+ function pluginConsts(w) {
32080
31993
  const consts = [`const icon =
32081
31994
  '${SURFACE_ICON}';`];
32082
31995
  if (w.features.settings) {
@@ -32085,6 +31998,10 @@ function pluginFile(w) {
32085
31998
  `const ${w.propertyName}Note = signal('');`
32086
31999
  );
32087
32000
  }
32001
+ if (w.features.command) consts.push(toneHelper());
32002
+ return consts;
32003
+ }
32004
+ function pluginBody(w) {
32088
32005
  const body = [` ctx.contributeIcons({ '${w.id}': icon });`];
32089
32006
  if (w.features.agent) {
32090
32007
  body.push(
@@ -32099,14 +32016,17 @@ function pluginFile(w) {
32099
32016
  if (w.features.menuSlot) body.push(menuBlock(w));
32100
32017
  if (w.features.settings) body.push(settingsBlock(w));
32101
32018
  if (w.features.agent) body.push(agentSurfaceBlock(w));
32019
+ return body;
32020
+ }
32021
+ function pluginFile(w) {
32022
+ const body = pluginBody(w);
32102
32023
  const deactivate = w.features.agent ? `
32103
32024
  deactivate() {
32104
32025
  ${w.propertyName}Agent.set(null);
32105
32026
  },` : "";
32106
- if (w.features.command) consts.push(toneHelper());
32107
- return `${imports.join("\n")}
32027
+ return `${pluginImports(w).join("\n")}
32108
32028
 
32109
- ${consts.join("\n\n")}
32029
+ ${pluginConsts(w).join("\n\n")}
32110
32030
 
32111
32031
  export const ${w.propertyName}Plugin: Plugin = {
32112
32032
  manifest: {
@@ -32120,6 +32040,109 @@ ${body.join("\n")}
32120
32040
  };
32121
32041
  `;
32122
32042
  }
32043
+
32044
+ // ../devkit/src/recipes/angular-weaver/recipe.ts
32045
+ var DEFAULT_MENU_SLOT = "content/tab/context";
32046
+ function accessLiteral(spec) {
32047
+ if (spec === "authenticated") return "{ authenticated: true }";
32048
+ if (spec === "anonymous") return "{ authenticated: false }";
32049
+ if (spec.startsWith("role:")) {
32050
+ const role = spec.slice("role:".length);
32051
+ if (!role)
32052
+ throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
32053
+ if (/['\\]/.test(role))
32054
+ throw new Error(
32055
+ `Access role "${role}" must not contain quotes or backslashes.`
32056
+ );
32057
+ return `{ anyRole: ['${role}'] }`;
32058
+ }
32059
+ throw new Error(
32060
+ `Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
32061
+ );
32062
+ }
32063
+ function resolveMenuSlot(menu) {
32064
+ if (menu === true) {
32065
+ return DEFAULT_MENU_SLOT;
32066
+ }
32067
+ return typeof menu === "string" && menu.length ? menu : void 0;
32068
+ }
32069
+ var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
32070
+ "cmd",
32071
+ "command",
32072
+ "ctrl",
32073
+ "control",
32074
+ "meta"
32075
+ ]);
32076
+ function assertPlatformNeutralChord(shortcut) {
32077
+ const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
32078
+ const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
32079
+ if (bound) {
32080
+ throw new Error(
32081
+ `Shortcut "${shortcut}" binds the platform-specific "${bound}" key. Use the neutral 'mod' token (e.g. 'mod+shift+k') \u2014 the host renders it as \u2318 on macOS and Ctrl elsewhere.`
32082
+ );
32083
+ }
32084
+ }
32085
+ function resolveFeatures(id, input) {
32086
+ const menuSlot = resolveMenuSlot(input?.menu);
32087
+ const barItem = Boolean(input?.barItem);
32088
+ const hasShortcut = Boolean(input?.shortcut?.trim());
32089
+ if (hasShortcut) {
32090
+ assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
32091
+ }
32092
+ const instanceable = Boolean(input?.instanceable);
32093
+ const container = Boolean(input?.container);
32094
+ if (instanceable && container) {
32095
+ throw new Error(
32096
+ 'A surface cannot be both a container and instanceable: a container tab holds its own ":id" and is therefore routable, while named instances exist only for a docked, non-routable surface. Pick one.'
32097
+ );
32098
+ }
32099
+ const agent = Boolean(input?.agent);
32100
+ return {
32101
+ command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
32102
+ menuSlot,
32103
+ settings: Boolean(input?.settings),
32104
+ access: input?.access ? accessLiteral(input.access) : void 0,
32105
+ shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
32106
+ barItem,
32107
+ about: Boolean(input?.about),
32108
+ instanceable,
32109
+ container,
32110
+ agent,
32111
+ spec: input?.spec !== false
32112
+ };
32113
+ }
32114
+ function deriveCapabilities(features) {
32115
+ const set2 = /* @__PURE__ */ new Set(["contributions", "navigation"]);
32116
+ if (features.command) set2.add("ui");
32117
+ if (features.about) {
32118
+ set2.add("ui");
32119
+ set2.add("host");
32120
+ }
32121
+ if (features.agent) {
32122
+ set2.add("ui");
32123
+ set2.add("automation");
32124
+ }
32125
+ return KNOWN_CAPABILITIES.filter((capability) => set2.has(capability));
32126
+ }
32127
+ function resolveWeaverInput(input) {
32128
+ if (!isKebabId(input.id)) {
32129
+ throw new Error(
32130
+ `Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
32131
+ );
32132
+ }
32133
+ const features = resolveFeatures(input.id, input.features);
32134
+ const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
32135
+ return {
32136
+ id: input.id,
32137
+ name: input.name?.trim() || toTitleCase(input.id),
32138
+ className: toPascalCase(input.id),
32139
+ propertyName: toCamelCase(input.id),
32140
+ capabilities,
32141
+ features,
32142
+ prefix: input.prefix?.trim() || "lw",
32143
+ importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
32144
+ };
32145
+ }
32123
32146
  function aboutDialogFile(w) {
32124
32147
  return `import { Component, inject } from '@angular/core';
32125
32148
  import { DialogRef, PluginHost } from '@loomweaver/plugin-sdk';
@@ -32430,9 +32453,13 @@ var framePlugin = {
32430
32453
  };
32431
32454
 
32432
32455
  // ../devkit/src/recipes/platform-version.ts
32433
- var PLATFORM_VERSION = "0.9.1";
32456
+ var PLATFORM_VERSION = "0.9.3";
32434
32457
 
32435
32458
  // ../devkit/src/recipes/angular-distribution/amendments.ts
32459
+ var DISTRIBUTION_INITIAL_BUDGET = {
32460
+ warning: "1.5MB",
32461
+ error: "2MB"
32462
+ };
32436
32463
  function distributionAmendments(d) {
32437
32464
  return [
32438
32465
  {
@@ -32466,7 +32493,8 @@ function distributionAmendments(d) {
32466
32493
  }
32467
32494
  ],
32468
32495
  serviceWorker: "ngsw-config.json",
32469
- inlineCritical: false
32496
+ inlineCritical: false,
32497
+ initialBudget: DISTRIBUTION_INITIAL_BUDGET
32470
32498
  }
32471
32499
  ];
32472
32500
  }
@@ -32661,9 +32689,10 @@ function readme2(d) {
32661
32689
  "the stylesheet with an **inline** `onload` handler that the policy blocks \u2014 the app then renders",
32662
32690
  "completely unstyled, and only in production builds.",
32663
32691
  "",
32664
- "One thing is still yours, because the scaffold cannot know your budget: a production build warns",
32665
- "that the initial bundle exceeds Angular's 500 kB default. The shell is a whole application",
32666
- "chrome, so raise the budgets in your build target.",
32692
+ "The initial-bundle budget in your build target was raised for the same reason: the shell is a",
32693
+ "whole application chrome and spends most of Angular's default before your own code arrives, so",
32694
+ "a production build would have failed on a number meant for an empty app. It is still a budget,",
32695
+ "and it is yours to move.",
32667
32696
  "",
32668
32697
  "## Ship less than the whole workbench",
32669
32698
  "",
@@ -32716,7 +32745,7 @@ function resolveDistributionInput(input) {
32716
32745
  );
32717
32746
  }
32718
32747
  const directory = input.directory === void 0 ? `apps/${input.name}` : input.directory.trim();
32719
- const depth = directory.split("/").filter(Boolean).length;
32748
+ const depth = normalizeProjectRoot(directory).split("/").filter(Boolean).length;
32720
32749
  return {
32721
32750
  name: input.name,
32722
32751
  title: input.title?.trim() || toTitleCase(input.name),
@@ -33436,10 +33465,10 @@ function weaverAmendments(input, where) {
33436
33465
  version: AG_UI_PROTOCOL_VERSION
33437
33466
  }
33438
33467
  ] : [];
33439
- const directory = normalizeProjectRoot(where ?? "");
33440
- if (!directory) {
33468
+ if (where === void 0 || where === "") {
33441
33469
  return packages;
33442
33470
  }
33471
+ const directory = normalizeProjectRoot(where);
33443
33472
  return [
33444
33473
  ...packages,
33445
33474
  {
@@ -33448,19 +33477,19 @@ function weaverAmendments(input, where) {
33448
33477
  assets: [
33449
33478
  {
33450
33479
  glob: "**/*.json",
33451
- input: `${directory}/src/lib/i18n`,
33480
+ input: joinProjectPath(directory, "src/lib/i18n"),
33452
33481
  from: "workspace",
33453
33482
  output: `i18n/${w.id}`
33454
33483
  }
33455
33484
  ]
33456
33485
  },
33457
- { kind: "stylesheet-source", sourceRoot: `${directory}/src` },
33486
+ { kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
33458
33487
  {
33459
33488
  kind: "compose-plugin",
33460
33489
  id: w.id,
33461
33490
  symbol: `${w.propertyName}Plugin`,
33462
33491
  capabilities: w.capabilities,
33463
- sourceRoot: `${directory}/src`
33492
+ sourceRoot: joinProjectPath(directory, "src")
33464
33493
  }
33465
33494
  ];
33466
33495
  }
@@ -33468,10 +33497,10 @@ function weaverAmendments(input, where) {
33468
33497
  // ../devkit/src/recipes/auth-source/recipe-amendments.ts
33469
33498
  function authSourceAmendments(input, where) {
33470
33499
  const a = resolveAuthSourceInput(input);
33471
- const directory = normalizeProjectRoot(where ?? "");
33472
- if (a.bare || !directory) {
33500
+ if (a.bare || where === void 0 || where === "") {
33473
33501
  return [];
33474
33502
  }
33503
+ const directory = normalizeProjectRoot(where);
33475
33504
  return [
33476
33505
  {
33477
33506
  kind: "build-target",
@@ -33479,7 +33508,7 @@ function authSourceAmendments(input, where) {
33479
33508
  assets: [
33480
33509
  {
33481
33510
  glob: "**/*.json",
33482
- input: `${directory}/i18n`,
33511
+ input: joinProjectPath(directory, "i18n"),
33483
33512
  from: "workspace",
33484
33513
  output: `i18n/${SESSION_PLUGIN_ID}`
33485
33514
  }
@@ -34166,7 +34195,7 @@ function read(ts, file2, call) {
34166
34195
  return {
34167
34196
  at: at2,
34168
34197
  id: id.text,
34169
- callable: callable !== void 0 && callable.kind === ts.SyntaxKind.TrueKeyword,
34198
+ callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
34170
34199
  described: properties.has("description"),
34171
34200
  answers: properties.has("answers"),
34172
34201
  returnsValue: returnsValue(ts, properties.get("run")),
@@ -34324,9 +34353,10 @@ function valuesFor(scaffold2, args) {
34324
34353
  }
34325
34354
  return values;
34326
34355
  }
34356
+ var WHERE_THE_FILES_LAND = "<the directory you wrote these files into>";
34327
34357
  function scaffold(scaffold2, args) {
34328
34358
  const values = valuesFor(scaffold2, args);
34329
- const remaining = (scaffold2.amend?.(values) ?? []).map((amendment) => describeAmendment(amendment));
34359
+ const remaining = (scaffold2.amend?.({ ...values, directory: WHERE_THE_FILES_LAND }) ?? []).map((amendment) => describeAmendment(amendment));
34330
34360
  return ok({
34331
34361
  files: scaffold2.build(values),
34332
34362
  ...remaining.length > 0 && { remaining }
@@ -34386,7 +34416,7 @@ function inputSchema(descriptor) {
34386
34416
  function createMcpServer() {
34387
34417
  const server = new McpServer({
34388
34418
  name: "loomweaver-devkit",
34389
- version: "0.9.1"
34419
+ version: "0.9.3"
34390
34420
  });
34391
34421
  server.registerTool(
34392
34422
  "list_generators",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/mcp",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "LoomWeaver: open-source plugin platform for Angular workbenches. The MCP server: scaffolding and validation as tools, so an AI assistant in any product repo can build weavers without a checkout.",
5
5
  "keywords": [
6
6
  "loomweaver",