@loomweaver/mcp 0.9.2 → 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 +146 -125
  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
  ] : [],
@@ -31450,7 +31458,7 @@ function pause(): Promise<void> {
31450
31458
  }
31451
31459
 
31452
31460
  // ../devkit/src/recipes/angular-weaver/agent-files.ts
31453
- var AG_UI_ADAPTER_VERSION = "0.9.2";
31461
+ var AG_UI_ADAPTER_VERSION = "0.9.3";
31454
31462
  var AG_UI_PROTOCOL_VERSION = "0.0.x";
31455
31463
  function connectionFile(w) {
31456
31464
  return `import { signal } from '@angular/core';
@@ -31817,109 +31825,8 @@ function toneHelper() {
31817
31825
  ].join("\n");
31818
31826
  }
31819
31827
 
31820
- // ../devkit/src/recipes/angular-weaver/recipe.ts
31821
- var DEFAULT_MENU_SLOT = "content/tab/context";
31828
+ // ../devkit/src/recipes/angular-weaver/weaver-plugin.ts
31822
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>';
31823
- function accessLiteral(spec) {
31824
- if (spec === "authenticated") return "{ authenticated: true }";
31825
- if (spec === "anonymous") return "{ authenticated: false }";
31826
- if (spec.startsWith("role:")) {
31827
- const role = spec.slice("role:".length);
31828
- if (!role)
31829
- throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
31830
- if (/['\\]/.test(role))
31831
- throw new Error(
31832
- `Access role "${role}" must not contain quotes or backslashes.`
31833
- );
31834
- return `{ anyRole: ['${role}'] }`;
31835
- }
31836
- throw new Error(
31837
- `Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
31838
- );
31839
- }
31840
- function resolveMenuSlot(menu) {
31841
- if (menu === true) {
31842
- return DEFAULT_MENU_SLOT;
31843
- }
31844
- return typeof menu === "string" && menu.length ? menu : void 0;
31845
- }
31846
- var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
31847
- "cmd",
31848
- "command",
31849
- "ctrl",
31850
- "control",
31851
- "meta"
31852
- ]);
31853
- function assertPlatformNeutralChord(shortcut) {
31854
- const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
31855
- const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
31856
- if (bound) {
31857
- throw new Error(
31858
- `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.`
31859
- );
31860
- }
31861
- }
31862
- function resolveFeatures(id, input) {
31863
- const menuSlot = resolveMenuSlot(input?.menu);
31864
- const barItem = Boolean(input?.barItem);
31865
- const hasShortcut = Boolean(input?.shortcut?.trim());
31866
- if (hasShortcut) {
31867
- assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
31868
- }
31869
- const instanceable = Boolean(input?.instanceable);
31870
- const container = Boolean(input?.container);
31871
- if (instanceable && container) {
31872
- throw new Error(
31873
- '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.'
31874
- );
31875
- }
31876
- const agent = Boolean(input?.agent);
31877
- return {
31878
- command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
31879
- menuSlot,
31880
- settings: Boolean(input?.settings),
31881
- access: input?.access ? accessLiteral(input.access) : void 0,
31882
- shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
31883
- barItem,
31884
- about: Boolean(input?.about),
31885
- instanceable,
31886
- container,
31887
- agent,
31888
- spec: input?.spec !== false
31889
- };
31890
- }
31891
- function deriveCapabilities(features) {
31892
- const set2 = /* @__PURE__ */ new Set(["contributions", "navigation"]);
31893
- if (features.command) set2.add("ui");
31894
- if (features.about) {
31895
- set2.add("ui");
31896
- set2.add("host");
31897
- }
31898
- if (features.agent) {
31899
- set2.add("ui");
31900
- set2.add("automation");
31901
- }
31902
- return KNOWN_CAPABILITIES.filter((capability) => set2.has(capability));
31903
- }
31904
- function resolveWeaverInput(input) {
31905
- if (!isKebabId(input.id)) {
31906
- throw new Error(
31907
- `Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
31908
- );
31909
- }
31910
- const features = resolveFeatures(input.id, input.features);
31911
- const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
31912
- return {
31913
- id: input.id,
31914
- name: input.name?.trim() || toTitleCase(input.id),
31915
- className: toPascalCase(input.id),
31916
- propertyName: toCamelCase(input.id),
31917
- capabilities,
31918
- features,
31919
- prefix: input.prefix?.trim() || "lw",
31920
- importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
31921
- };
31922
- }
31923
31830
  function containerChildIds(w) {
31924
31831
  return [`${w.id}.canvas`, `${w.id}.details`];
31925
31832
  }
@@ -32056,7 +31963,7 @@ function settingsBlock(w) {
32056
31963
  " });"
32057
31964
  ].join("\n");
32058
31965
  }
32059
- function pluginFile(w) {
31966
+ function pluginImports(w) {
32060
31967
  const imports = ["import { Plugin } from '@loomweaver/plugin-sdk';"];
32061
31968
  if (w.features.container) {
32062
31969
  imports.push(
@@ -32080,6 +31987,9 @@ function pluginFile(w) {
32080
31987
  if (w.features.settings) {
32081
31988
  imports.unshift("import { signal } from '@angular/core';");
32082
31989
  }
31990
+ return imports;
31991
+ }
31992
+ function pluginConsts(w) {
32083
31993
  const consts = [`const icon =
32084
31994
  '${SURFACE_ICON}';`];
32085
31995
  if (w.features.settings) {
@@ -32088,6 +31998,10 @@ function pluginFile(w) {
32088
31998
  `const ${w.propertyName}Note = signal('');`
32089
31999
  );
32090
32000
  }
32001
+ if (w.features.command) consts.push(toneHelper());
32002
+ return consts;
32003
+ }
32004
+ function pluginBody(w) {
32091
32005
  const body = [` ctx.contributeIcons({ '${w.id}': icon });`];
32092
32006
  if (w.features.agent) {
32093
32007
  body.push(
@@ -32102,14 +32016,17 @@ function pluginFile(w) {
32102
32016
  if (w.features.menuSlot) body.push(menuBlock(w));
32103
32017
  if (w.features.settings) body.push(settingsBlock(w));
32104
32018
  if (w.features.agent) body.push(agentSurfaceBlock(w));
32019
+ return body;
32020
+ }
32021
+ function pluginFile(w) {
32022
+ const body = pluginBody(w);
32105
32023
  const deactivate = w.features.agent ? `
32106
32024
  deactivate() {
32107
32025
  ${w.propertyName}Agent.set(null);
32108
32026
  },` : "";
32109
- if (w.features.command) consts.push(toneHelper());
32110
- return `${imports.join("\n")}
32027
+ return `${pluginImports(w).join("\n")}
32111
32028
 
32112
- ${consts.join("\n\n")}
32029
+ ${pluginConsts(w).join("\n\n")}
32113
32030
 
32114
32031
  export const ${w.propertyName}Plugin: Plugin = {
32115
32032
  manifest: {
@@ -32123,6 +32040,109 @@ ${body.join("\n")}
32123
32040
  };
32124
32041
  `;
32125
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
+ }
32126
32146
  function aboutDialogFile(w) {
32127
32147
  return `import { Component, inject } from '@angular/core';
32128
32148
  import { DialogRef, PluginHost } from '@loomweaver/plugin-sdk';
@@ -32433,7 +32453,7 @@ var framePlugin = {
32433
32453
  };
32434
32454
 
32435
32455
  // ../devkit/src/recipes/platform-version.ts
32436
- var PLATFORM_VERSION = "0.9.2";
32456
+ var PLATFORM_VERSION = "0.9.3";
32437
32457
 
32438
32458
  // ../devkit/src/recipes/angular-distribution/amendments.ts
32439
32459
  var DISTRIBUTION_INITIAL_BUDGET = {
@@ -32725,7 +32745,7 @@ function resolveDistributionInput(input) {
32725
32745
  );
32726
32746
  }
32727
32747
  const directory = input.directory === void 0 ? `apps/${input.name}` : input.directory.trim();
32728
- const depth = directory.split("/").filter(Boolean).length;
32748
+ const depth = normalizeProjectRoot(directory).split("/").filter(Boolean).length;
32729
32749
  return {
32730
32750
  name: input.name,
32731
32751
  title: input.title?.trim() || toTitleCase(input.name),
@@ -33445,10 +33465,10 @@ function weaverAmendments(input, where) {
33445
33465
  version: AG_UI_PROTOCOL_VERSION
33446
33466
  }
33447
33467
  ] : [];
33448
- const directory = normalizeProjectRoot(where ?? "");
33449
- if (!directory) {
33468
+ if (where === void 0 || where === "") {
33450
33469
  return packages;
33451
33470
  }
33471
+ const directory = normalizeProjectRoot(where);
33452
33472
  return [
33453
33473
  ...packages,
33454
33474
  {
@@ -33457,19 +33477,19 @@ function weaverAmendments(input, where) {
33457
33477
  assets: [
33458
33478
  {
33459
33479
  glob: "**/*.json",
33460
- input: `${directory}/src/lib/i18n`,
33480
+ input: joinProjectPath(directory, "src/lib/i18n"),
33461
33481
  from: "workspace",
33462
33482
  output: `i18n/${w.id}`
33463
33483
  }
33464
33484
  ]
33465
33485
  },
33466
- { kind: "stylesheet-source", sourceRoot: `${directory}/src` },
33486
+ { kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
33467
33487
  {
33468
33488
  kind: "compose-plugin",
33469
33489
  id: w.id,
33470
33490
  symbol: `${w.propertyName}Plugin`,
33471
33491
  capabilities: w.capabilities,
33472
- sourceRoot: `${directory}/src`
33492
+ sourceRoot: joinProjectPath(directory, "src")
33473
33493
  }
33474
33494
  ];
33475
33495
  }
@@ -33477,10 +33497,10 @@ function weaverAmendments(input, where) {
33477
33497
  // ../devkit/src/recipes/auth-source/recipe-amendments.ts
33478
33498
  function authSourceAmendments(input, where) {
33479
33499
  const a = resolveAuthSourceInput(input);
33480
- const directory = normalizeProjectRoot(where ?? "");
33481
- if (a.bare || !directory) {
33500
+ if (a.bare || where === void 0 || where === "") {
33482
33501
  return [];
33483
33502
  }
33503
+ const directory = normalizeProjectRoot(where);
33484
33504
  return [
33485
33505
  {
33486
33506
  kind: "build-target",
@@ -33488,7 +33508,7 @@ function authSourceAmendments(input, where) {
33488
33508
  assets: [
33489
33509
  {
33490
33510
  glob: "**/*.json",
33491
- input: `${directory}/i18n`,
33511
+ input: joinProjectPath(directory, "i18n"),
33492
33512
  from: "workspace",
33493
33513
  output: `i18n/${SESSION_PLUGIN_ID}`
33494
33514
  }
@@ -34175,7 +34195,7 @@ function read(ts, file2, call) {
34175
34195
  return {
34176
34196
  at: at2,
34177
34197
  id: id.text,
34178
- callable: callable !== void 0 && callable.kind === ts.SyntaxKind.TrueKeyword,
34198
+ callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
34179
34199
  described: properties.has("description"),
34180
34200
  answers: properties.has("answers"),
34181
34201
  returnsValue: returnsValue(ts, properties.get("run")),
@@ -34333,9 +34353,10 @@ function valuesFor(scaffold2, args) {
34333
34353
  }
34334
34354
  return values;
34335
34355
  }
34356
+ var WHERE_THE_FILES_LAND = "<the directory you wrote these files into>";
34336
34357
  function scaffold(scaffold2, args) {
34337
34358
  const values = valuesFor(scaffold2, args);
34338
- const remaining = (scaffold2.amend?.(values) ?? []).map((amendment) => describeAmendment(amendment));
34359
+ const remaining = (scaffold2.amend?.({ ...values, directory: WHERE_THE_FILES_LAND }) ?? []).map((amendment) => describeAmendment(amendment));
34339
34360
  return ok({
34340
34361
  files: scaffold2.build(values),
34341
34362
  ...remaining.length > 0 && { remaining }
@@ -34395,7 +34416,7 @@ function inputSchema(descriptor) {
34395
34416
  function createMcpServer() {
34396
34417
  const server = new McpServer({
34397
34418
  name: "loomweaver-devkit",
34398
- version: "0.9.2"
34419
+ version: "0.9.3"
34399
34420
  });
34400
34421
  server.registerTool(
34401
34422
  "list_generators",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomweaver/mcp",
3
- "version": "0.9.2",
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",