@loomweaver/mcp 0.9.2 → 0.9.4

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 +147 -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.4";
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.4";
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),
@@ -33368,6 +33388,7 @@ function bootstrapFile(t) {
33368
33388
  --lw-content: var(--bs-body-color);
33369
33389
  --lw-content-muted: var(--bs-secondary-color);
33370
33390
  --lw-content-faint: var(--bs-secondary-color);
33391
+ --lw-unsaved: var(--bs-warning-text-emphasis);
33371
33392
 
33372
33393
  --lw-tooltip: var(--bs-emphasis-color);
33373
33394
  --lw-tooltip-content: var(--bs-body-bg);
@@ -33445,10 +33466,10 @@ function weaverAmendments(input, where) {
33445
33466
  version: AG_UI_PROTOCOL_VERSION
33446
33467
  }
33447
33468
  ] : [];
33448
- const directory = normalizeProjectRoot(where ?? "");
33449
- if (!directory) {
33469
+ if (where === void 0 || where === "") {
33450
33470
  return packages;
33451
33471
  }
33472
+ const directory = normalizeProjectRoot(where);
33452
33473
  return [
33453
33474
  ...packages,
33454
33475
  {
@@ -33457,19 +33478,19 @@ function weaverAmendments(input, where) {
33457
33478
  assets: [
33458
33479
  {
33459
33480
  glob: "**/*.json",
33460
- input: `${directory}/src/lib/i18n`,
33481
+ input: joinProjectPath(directory, "src/lib/i18n"),
33461
33482
  from: "workspace",
33462
33483
  output: `i18n/${w.id}`
33463
33484
  }
33464
33485
  ]
33465
33486
  },
33466
- { kind: "stylesheet-source", sourceRoot: `${directory}/src` },
33487
+ { kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
33467
33488
  {
33468
33489
  kind: "compose-plugin",
33469
33490
  id: w.id,
33470
33491
  symbol: `${w.propertyName}Plugin`,
33471
33492
  capabilities: w.capabilities,
33472
- sourceRoot: `${directory}/src`
33493
+ sourceRoot: joinProjectPath(directory, "src")
33473
33494
  }
33474
33495
  ];
33475
33496
  }
@@ -33477,10 +33498,10 @@ function weaverAmendments(input, where) {
33477
33498
  // ../devkit/src/recipes/auth-source/recipe-amendments.ts
33478
33499
  function authSourceAmendments(input, where) {
33479
33500
  const a = resolveAuthSourceInput(input);
33480
- const directory = normalizeProjectRoot(where ?? "");
33481
- if (a.bare || !directory) {
33501
+ if (a.bare || where === void 0 || where === "") {
33482
33502
  return [];
33483
33503
  }
33504
+ const directory = normalizeProjectRoot(where);
33484
33505
  return [
33485
33506
  {
33486
33507
  kind: "build-target",
@@ -33488,7 +33509,7 @@ function authSourceAmendments(input, where) {
33488
33509
  assets: [
33489
33510
  {
33490
33511
  glob: "**/*.json",
33491
- input: `${directory}/i18n`,
33512
+ input: joinProjectPath(directory, "i18n"),
33492
33513
  from: "workspace",
33493
33514
  output: `i18n/${SESSION_PLUGIN_ID}`
33494
33515
  }
@@ -34175,7 +34196,7 @@ function read(ts, file2, call) {
34175
34196
  return {
34176
34197
  at: at2,
34177
34198
  id: id.text,
34178
- callable: callable !== void 0 && callable.kind === ts.SyntaxKind.TrueKeyword,
34199
+ callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
34179
34200
  described: properties.has("description"),
34180
34201
  answers: properties.has("answers"),
34181
34202
  returnsValue: returnsValue(ts, properties.get("run")),
@@ -34333,9 +34354,10 @@ function valuesFor(scaffold2, args) {
34333
34354
  }
34334
34355
  return values;
34335
34356
  }
34357
+ var WHERE_THE_FILES_LAND = "<the directory you wrote these files into>";
34336
34358
  function scaffold(scaffold2, args) {
34337
34359
  const values = valuesFor(scaffold2, args);
34338
- const remaining = (scaffold2.amend?.(values) ?? []).map((amendment) => describeAmendment(amendment));
34360
+ const remaining = (scaffold2.amend?.({ ...values, directory: WHERE_THE_FILES_LAND }) ?? []).map((amendment) => describeAmendment(amendment));
34339
34361
  return ok({
34340
34362
  files: scaffold2.build(values),
34341
34363
  ...remaining.length > 0 && { remaining }
@@ -34395,7 +34417,7 @@ function inputSchema(descriptor) {
34395
34417
  function createMcpServer() {
34396
34418
  const server = new McpServer({
34397
34419
  name: "loomweaver-devkit",
34398
- version: "0.9.2"
34420
+ version: "0.9.4"
34399
34421
  });
34400
34422
  server.registerTool(
34401
34423
  "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.4",
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",