@loomweaver/mcp 0.14.1 → 0.14.2

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 +1367 -1267
  3. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -101,7 +101,7 @@ var require_code = __commonJS({
101
101
  }
102
102
  exports._ = _;
103
103
  var plus = new _Code("+");
104
- function str2(strs, ...args) {
104
+ function str(strs, ...args) {
105
105
  const expr = [safeStringify(strs[0])];
106
106
  let i = 0;
107
107
  while (i < args.length) {
@@ -112,7 +112,7 @@ var require_code = __commonJS({
112
112
  optimize(expr);
113
113
  return new _Code(expr);
114
114
  }
115
- exports.str = str2;
115
+ exports.str = str;
116
116
  function addCodeArg(code, arg) {
117
117
  if (arg instanceof _Code)
118
118
  code.push(...arg._items);
@@ -155,7 +155,7 @@ var require_code = __commonJS({
155
155
  return;
156
156
  }
157
157
  function strConcat(c1, c2) {
158
- return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
158
+ return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
159
159
  }
160
160
  exports.strConcat = strConcat;
161
161
  function interpolate(x) {
@@ -1117,22 +1117,22 @@ var require_util = __commonJS({
1117
1117
  return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
1118
1118
  }
1119
1119
  exports.schemaRefOrVal = schemaRefOrVal;
1120
- function unescapeFragment(str2) {
1121
- return unescapeJsonPointer(decodeURIComponent(str2));
1120
+ function unescapeFragment(str) {
1121
+ return unescapeJsonPointer(decodeURIComponent(str));
1122
1122
  }
1123
1123
  exports.unescapeFragment = unescapeFragment;
1124
- function escapeFragment(str2) {
1125
- return encodeURIComponent(escapeJsonPointer(str2));
1124
+ function escapeFragment(str) {
1125
+ return encodeURIComponent(escapeJsonPointer(str));
1126
1126
  }
1127
1127
  exports.escapeFragment = escapeFragment;
1128
- function escapeJsonPointer(str2) {
1129
- if (typeof str2 == "number")
1130
- return `${str2}`;
1131
- return str2.replace(/~/g, "~0").replace(/\//g, "~1");
1128
+ function escapeJsonPointer(str) {
1129
+ if (typeof str == "number")
1130
+ return `${str}`;
1131
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
1132
1132
  }
1133
1133
  exports.escapeJsonPointer = escapeJsonPointer;
1134
- function unescapeJsonPointer(str2) {
1135
- return str2.replace(/~1/g, "/").replace(/~0/g, "~");
1134
+ function unescapeJsonPointer(str) {
1135
+ return str.replace(/~1/g, "/").replace(/~0/g, "~");
1136
1136
  }
1137
1137
  exports.unescapeJsonPointer = unescapeJsonPointer;
1138
1138
  function eachItem(xs, f) {
@@ -2157,8 +2157,8 @@ var require_json_schema_traverse = __commonJS({
2157
2157
  post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
2158
2158
  }
2159
2159
  }
2160
- function escapeJsonPtr(str2) {
2161
- return str2.replace(/~/g, "~0").replace(/\//g, "~1");
2160
+ function escapeJsonPtr(str) {
2161
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
2162
2162
  }
2163
2163
  }
2164
2164
  });
@@ -3222,10 +3222,10 @@ var require_utils = __commonJS({
3222
3222
  return { host, isIPV6: false };
3223
3223
  }
3224
3224
  }
3225
- function findToken(str2, token) {
3225
+ function findToken(str, token) {
3226
3226
  let ind = 0;
3227
- for (let i = 0; i < str2.length; i++) {
3228
- if (str2[i] === token) ind++;
3227
+ for (let i = 0; i < str.length; i++) {
3228
+ if (str[i] === token) ind++;
3229
3229
  }
3230
3230
  return ind;
3231
3231
  }
@@ -3988,7 +3988,7 @@ var require_core = __commonJS({
3988
3988
  var util_1 = require_util();
3989
3989
  var $dataRefSchema = require_data();
3990
3990
  var uri_1 = require_uri();
3991
- var defaultRegExp = (str2, flags) => new RegExp(str2, flags);
3991
+ var defaultRegExp = (str, flags) => new RegExp(str, flags);
3992
3992
  defaultRegExp.code = "new RegExp";
3993
3993
  var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"];
3994
3994
  var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([
@@ -4783,16 +4783,16 @@ var require_ucs2length = __commonJS({
4783
4783
  "../../../node_modules/ajv/dist/runtime/ucs2length.js"(exports) {
4784
4784
  "use strict";
4785
4785
  Object.defineProperty(exports, "__esModule", { value: true });
4786
- function ucs2length(str2) {
4787
- const len = str2.length;
4786
+ function ucs2length(str) {
4787
+ const len = str.length;
4788
4788
  let length = 0;
4789
4789
  let pos = 0;
4790
4790
  let value;
4791
4791
  while (pos < len) {
4792
4792
  length++;
4793
- value = str2.charCodeAt(pos++);
4793
+ value = str.charCodeAt(pos++);
4794
4794
  if (value >= 55296 && value <= 56319 && pos < len) {
4795
- value = str2.charCodeAt(pos);
4795
+ value = str.charCodeAt(pos);
4796
4796
  if ((value & 64512) === 56320)
4797
4797
  pos++;
4798
4798
  }
@@ -6675,8 +6675,8 @@ var require_formats = __commonJS({
6675
6675
  }
6676
6676
  var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
6677
6677
  var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
6678
- function date5(str2) {
6679
- const matches = DATE.exec(str2);
6678
+ function date5(str) {
6679
+ const matches = DATE.exec(str);
6680
6680
  if (!matches)
6681
6681
  return false;
6682
6682
  const year = +matches[1];
@@ -6695,8 +6695,8 @@ var require_formats = __commonJS({
6695
6695
  }
6696
6696
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
6697
6697
  function getTime(strictTimeZone) {
6698
- return function time3(str2) {
6699
- const matches = TIME.exec(str2);
6698
+ return function time3(str) {
6699
+ const matches = TIME.exec(str);
6700
6700
  if (!matches)
6701
6701
  return false;
6702
6702
  const hr = +matches[1];
@@ -6742,8 +6742,8 @@ var require_formats = __commonJS({
6742
6742
  var DATE_TIME_SEPARATOR = /t|\s/i;
6743
6743
  function getDateTime(strictTimeZone) {
6744
6744
  const time3 = getTime(strictTimeZone);
6745
- return function date_time(str2) {
6746
- const dateTime = str2.split(DATE_TIME_SEPARATOR);
6745
+ return function date_time(str) {
6746
+ const dateTime = str.split(DATE_TIME_SEPARATOR);
6747
6747
  return dateTime.length === 2 && date5(dateTime[0]) && time3(dateTime[1]);
6748
6748
  };
6749
6749
  }
@@ -6768,13 +6768,13 @@ var require_formats = __commonJS({
6768
6768
  }
6769
6769
  var NOT_URI_FRAGMENT = /\/|:/;
6770
6770
  var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
6771
- function uri(str2) {
6772
- return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
6771
+ function uri(str) {
6772
+ return NOT_URI_FRAGMENT.test(str) && URI.test(str);
6773
6773
  }
6774
6774
  var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
6775
- function byte(str2) {
6775
+ function byte(str) {
6776
6776
  BYTE.lastIndex = 0;
6777
- return BYTE.test(str2);
6777
+ return BYTE.test(str);
6778
6778
  }
6779
6779
  var MIN_INT32 = -(2 ** 31);
6780
6780
  var MAX_INT32 = 2 ** 31 - 1;
@@ -6788,11 +6788,11 @@ var require_formats = __commonJS({
6788
6788
  return true;
6789
6789
  }
6790
6790
  var Z_ANCHOR = /[^\\]\\Z/;
6791
- function regex(str2) {
6792
- if (Z_ANCHOR.test(str2))
6791
+ function regex(str) {
6792
+ if (Z_ANCHOR.test(str))
6793
6793
  return false;
6794
6794
  try {
6795
- new RegExp(str2);
6795
+ new RegExp(str);
6796
6796
  return true;
6797
6797
  } catch (e) {
6798
6798
  return false;
@@ -7702,14 +7702,14 @@ function promiseAllObject(promisesObj) {
7702
7702
  }
7703
7703
  function randomString(length = 10) {
7704
7704
  const chars = "abcdefghijklmnopqrstuvwxyz";
7705
- let str2 = "";
7705
+ let str = "";
7706
7706
  for (let i = 0; i < length; i++) {
7707
- str2 += chars[Math.floor(Math.random() * chars.length)];
7707
+ str += chars[Math.floor(Math.random() * chars.length)];
7708
7708
  }
7709
- return str2;
7709
+ return str;
7710
7710
  }
7711
- function esc(str2) {
7712
- return JSON.stringify(str2);
7711
+ function esc(str) {
7712
+ return JSON.stringify(str);
7713
7713
  }
7714
7714
  function slugify(input) {
7715
7715
  return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -7823,8 +7823,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
7823
7823
  "symbol",
7824
7824
  "undefined"
7825
7825
  ]);
7826
- function escapeRegex(str2) {
7827
- return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7826
+ function escapeRegex(str) {
7827
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7828
7828
  }
7829
7829
  function clone(inst, def, params) {
7830
7830
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -31030,49 +31030,91 @@ function joinProjectPath(projectRoot, path) {
31030
31030
  return root ? `${root}/${path}` : path;
31031
31031
  }
31032
31032
 
31033
+ // ../devkit/src/lib/amend/compose.ts
31034
+ function registrationLines(amendment, providers) {
31035
+ return [
31036
+ ...providers.map((provider) => provider.line),
31037
+ namespaceLine(amendment.id),
31038
+ `provideCapabilityGrants({ ${amendment.id}: [${quotedList(amendment.capabilities)}] }),`,
31039
+ `...providePlugins(${amendment.symbol}),`
31040
+ ];
31041
+ }
31042
+ function quotedList(values) {
31043
+ return values.map((value) => `'${value}'`).join(", ");
31044
+ }
31045
+ function namespaceLine(id) {
31046
+ return `provideTranslationNamespaces('${id}'),`;
31047
+ }
31048
+
31033
31049
  // ../devkit/src/lib/amend/describe.ts
31034
31050
  function describeAmendment(amendment) {
31035
- if (amendment.kind === "postcss") {
31036
- return `Write ${amendment.file} beside your package.json, naming ${amendment.plugin}. Without it the stylesheet is read as plain CSS: no utility class is emitted, the workbench renders unstyled, and the build still reports success.`;
31037
- }
31038
- if (amendment.kind === "package") {
31039
- return `Add ${amendment.name}@${amendment.version} to your project's dependencies and install it. Without it the generated files import a package that is not there, so the very first build fails.`;
31040
- }
31041
- if (amendment.kind === "stylesheet-source") {
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.`;
31051
+ switch (amendment.kind) {
31052
+ case "postcss": {
31053
+ return `Write ${amendment.file} beside your package.json, naming ${amendment.plugin}. Without it the stylesheet is read as plain CSS: no utility class is emitted, the workbench renders unstyled, and the build still reports success.`;
31054
+ }
31055
+ case "package": {
31056
+ return `Add ${amendment.name}@${amendment.version} to your project's dependencies and install it. Without it the generated files import a package that is not there, so the very first build fails.`;
31057
+ }
31058
+ case "stylesheet-source": {
31059
+ 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.`;
31060
+ }
31061
+ case "compose-plugin": {
31062
+ return describeRegistration(amendment);
31063
+ }
31064
+ case "build-target": {
31065
+ return describeBuildTarget(amendment);
31066
+ }
31067
+ default: {
31068
+ const unknown2 = amendment;
31069
+ return unknown2;
31070
+ }
31043
31071
  }
31044
- if (amendment.kind === "compose-plugin") {
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.`;
31072
+ }
31073
+ function describeRegistration(amendment) {
31074
+ const calls = registrationLines(amendment, amendment.providers ?? []).map(
31075
+ (line) => line.replace(/,$/, "")
31076
+ );
31077
+ const imports = (amendment.providers ?? []).flatMap((provider) => provider.from ?? []).map(
31078
+ (imported) => ` import { ${imported.symbols.join(", ")} } from '${imported.path}'.`
31079
+ ).join("");
31080
+ return `Register ${amendment.id} in the composition root: import { ${amendment.symbol} }, ${calls.slice(0, -1).join(", ")} and ${calls.at(-1)}.${imports} Without it none of its contributions appear.`;
31081
+ }
31082
+ function describeBuildTarget(amendment) {
31083
+ const steps = [];
31084
+ if (amendment.styles.length > 0) {
31085
+ steps.push(`name ${amendment.styles.join(", ")} in styles`);
31086
+ }
31087
+ if (amendment.assets.length > 0) {
31088
+ const assets = amendment.assets.map(
31089
+ (asset) => asset.output ? `${asset.input} served under ${asset.output}` : asset.input
31090
+ ).join(", ");
31091
+ steps.push(
31092
+ `add assets for ${assets} (the shell fetches its own strings at runtime, so without that glob every label in the chrome renders as its raw translation key)`
31093
+ );
31051
31094
  }
31052
- return [
31053
- ...amendment.styles.length > 0 ? [`name ${amendment.styles.join(", ")} in styles`] : [],
31054
- ...amendment.assets.length > 0 ? [
31055
- `add assets for ${amendment.assets.map(
31056
- (asset) => asset.output ? `${asset.input} served under ${asset.output}` : asset.input
31057
- ).join(
31058
- ", "
31059
- )} (the shell fetches its own strings at runtime, so without that glob every label in the chrome renders as its raw translation key)`
31060
- ] : [],
31061
- ...amendment.serviceWorker ? [
31095
+ if (amendment.serviceWorker) {
31096
+ steps.push(
31062
31097
  `set serviceWorker to ${amendment.serviceWorker} in the production configuration (provideShell registers a worker that 404s otherwise)`
31063
- ] : [],
31064
- ...amendment.inlineCritical === void 0 ? [] : [
31098
+ );
31099
+ }
31100
+ if (amendment.inlineCritical !== void 0) {
31101
+ steps.push(
31065
31102
  `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)`
31066
- ],
31067
- ...amendment.initialBudget ? [
31103
+ );
31104
+ }
31105
+ if (amendment.initialBudget) {
31106
+ steps.push(
31068
31107
  `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
- ] : []
31070
- ].map((step, index) => `${index + 1}. ${step}`).join(" ");
31108
+ );
31109
+ }
31110
+ return steps.map((step, index) => `${index + 1}. ${step}`).join(" ");
31071
31111
  }
31072
31112
 
31073
31113
  // ../devkit/src/lib/generate/casing.ts
31114
+ var KEBAB_ID_PATTERN = "^[a-z][a-z0-9]*(-[a-z0-9]+)*$";
31115
+ var KEBAB_ID = new RegExp(KEBAB_ID_PATTERN);
31074
31116
  function isKebabId(value) {
31075
- return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value);
31117
+ return KEBAB_ID.test(value);
31076
31118
  }
31077
31119
  function words(value) {
31078
31120
  return value.split(/[-_\s]+/).filter(Boolean);
@@ -31088,82 +31130,33 @@ function toTitleCase(value) {
31088
31130
  return words(value).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
31089
31131
  }
31090
31132
 
31091
- // ../devkit/src/lib/validate/manifest.ts
31092
- var KNOWN_CAPABILITIES = [
31093
- "contributions",
31094
- "ui",
31095
- "host",
31096
- "navigation",
31097
- "session",
31098
- "theme",
31099
- "automation"
31133
+ // ../devkit/src/recipes/shell-regions.ts
31134
+ var TOP_BAR_REGION = "top-bar";
31135
+ var RAIL_REGION = "primary";
31136
+ var LEFT_PANEL_REGION = "left-panel";
31137
+ var RIGHT_PANEL_REGION = "right-panel";
31138
+ var CONTENT_REGION = "main";
31139
+ var STATUS_BAR_REGION = "status-bar";
31140
+ var SHELL_REGIONS = [
31141
+ { id: TOP_BAR_REGION, type: "bar", dock: "top" },
31142
+ { id: RAIL_REGION, type: "rail", dock: "left" },
31143
+ { id: LEFT_PANEL_REGION, type: "panel", dock: "left" },
31144
+ { id: RIGHT_PANEL_REGION, type: "panel", dock: "right" },
31145
+ { id: CONTENT_REGION, type: "content", dock: "center" },
31146
+ { id: STATUS_BAR_REGION, type: "bar", dock: "bottom" }
31100
31147
  ];
31101
- function validateManifest(manifest2, known = KNOWN_CAPABILITIES) {
31102
- const findings = [];
31103
- if (typeof manifest2.id !== "string" || !isKebabId(manifest2.id)) {
31104
- findings.push({
31105
- level: "error",
31106
- code: "manifest.id",
31107
- message: `Plugin id must be a kebab-case string; got ${JSON.stringify(manifest2.id)}.`,
31108
- path: "manifest.id"
31109
- });
31110
- }
31111
- if (manifest2.name !== void 0 && typeof manifest2.name !== "string") {
31112
- findings.push({
31113
- level: "error",
31114
- code: "manifest.name",
31115
- message: "Plugin name must be a string when provided.",
31116
- path: "manifest.name"
31117
- });
31118
- }
31119
- findings.push(...validateCapabilities(manifest2.capabilities, known));
31120
- return findings;
31121
- }
31122
- function validateCapabilities(capabilities, known) {
31123
- if (capabilities === void 0) {
31124
- return [];
31125
- }
31126
- if (!Array.isArray(capabilities)) {
31127
- return [
31128
- {
31129
- level: "error",
31130
- code: "manifest.capabilities",
31131
- message: "capabilities must be an array when provided.",
31132
- path: "manifest.capabilities"
31133
- }
31134
- ];
31135
- }
31136
- const findings = [];
31137
- const seen = /* @__PURE__ */ new Set();
31138
- for (const capability of capabilities) {
31139
- if (typeof capability !== "string" || !known.includes(capability)) {
31140
- findings.push({
31141
- level: "error",
31142
- code: "manifest.capability.unknown",
31143
- message: `Unknown capability ${JSON.stringify(capability)}. Known: ${known.join(", ")}.`,
31144
- path: "manifest.capabilities"
31145
- });
31146
- continue;
31147
- }
31148
- if (seen.has(capability)) {
31149
- findings.push({
31150
- level: "warning",
31151
- code: "manifest.capability.duplicate",
31152
- message: `Duplicate capability "${capability}".`,
31153
- path: "manifest.capabilities"
31154
- });
31155
- }
31156
- seen.add(capability);
31157
- }
31158
- return findings;
31148
+ function renderRegions(indent) {
31149
+ return SHELL_REGIONS.map(
31150
+ ({ id, type, dock }) => `${indent}{ id: '${id}', type: '${type}', dock: '${dock}' },`
31151
+ ).join("\n");
31159
31152
  }
31160
31153
 
31161
31154
  // ../devkit/src/recipes/angular-weaver/agent-panel.ts
31162
- function panelFile(w) {
31155
+ function panelFile(weaver) {
31163
31156
  return `import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
31164
- import { EventType, type BaseEvent, type Tool } from '@ag-ui/core';
31165
- import { ${w.propertyName}Agent } from './${w.id}-agent';
31166
- import { askAgent } from './${w.id}-agent-source';
31157
+ import { EventType, type BaseEvent, type Tool, type ToolMessage } from '@ag-ui/core';
31158
+ import { ${weaver.propertyName}Agent } from './${weaver.id}-agent';
31159
+ import { askAgent } from './${weaver.id}-agent-source';
31167
31160
 
31168
31161
  interface Line {
31169
31162
  readonly kind: 'you' | 'agent' | 'call' | 'result' | 'note';
@@ -31173,11 +31166,11 @@ interface Line {
31173
31166
  }
31174
31167
 
31175
31168
  @Component({
31176
- selector: '${w.prefix}-${w.id}-agent-panel',
31177
- templateUrl: './${w.id}-agent-panel.html',
31169
+ selector: '${weaver.prefix}-${weaver.id}-agent-panel',
31170
+ templateUrl: './${weaver.id}-agent-panel.html',
31178
31171
  changeDetection: ChangeDetectionStrategy.OnPush,
31179
31172
  })
31180
- export class ${w.className}AgentPanel {
31173
+ export class ${weaver.className}AgentPanel {
31181
31174
  protected readonly offered = signal<readonly Tool[]>([]);
31182
31175
 
31183
31176
  protected readonly lines = signal<readonly Line[]>([]);
@@ -31193,11 +31186,11 @@ export class ${w.className}AgentPanel {
31193
31186
  // The list is read again whenever the panel gains focus, because the weaver's own translations
31194
31187
  // and other plugins' commands may arrive after this component was built.
31195
31188
  protected refreshOffered(): void {
31196
- this.offered.set(${w.propertyName}Agent()?.list() ?? []);
31189
+ this.offered.set(${weaver.propertyName}Agent()?.list() ?? []);
31197
31190
  }
31198
31191
 
31199
31192
  protected async ask(name: string): Promise<void> {
31200
- const tools = ${w.propertyName}Agent();
31193
+ const tools = ${weaver.propertyName}Agent();
31201
31194
  if (!tools || this.busy()) {
31202
31195
  return;
31203
31196
  }
@@ -31219,29 +31212,27 @@ export class ${w.className}AgentPanel {
31219
31212
  this.draw(event);
31220
31213
  // Every event goes over, unfiltered. The adapter decides which ones matter; a filter here is
31221
31214
  // how a call ends up half-assembled.
31222
- const message = await tools.receive(event);
31223
- if (message) {
31224
- this.push({
31225
- kind: 'result',
31226
- text: message.error ?? message.content,
31227
- failed: Boolean(message.error),
31228
- });
31229
- }
31215
+ this.pushAnswer(await tools.receive(event));
31230
31216
  }
31231
- // A run that ends without its closing event leaves a call open; flush() answers it.
31232
- const last = await tools.flush();
31233
- if (last) {
31234
- this.push({
31235
- kind: 'result',
31236
- text: last.error ?? last.content,
31237
- failed: Boolean(last.error),
31238
- });
31217
+ // Every call the agent left open is answered, one per flush(), until none is left.
31218
+ for (let left = await tools.flush(); left; left = await tools.flush()) {
31219
+ this.pushAnswer(left);
31239
31220
  }
31240
31221
  } finally {
31241
31222
  this.busy.set(false);
31242
31223
  }
31243
31224
  }
31244
31225
 
31226
+ private pushAnswer(message: ToolMessage | null): void {
31227
+ if (message) {
31228
+ this.push({
31229
+ kind: 'result',
31230
+ text: message.error ?? message.content,
31231
+ failed: Boolean(message.error),
31232
+ });
31233
+ }
31234
+ }
31235
+
31245
31236
  private draw(event: BaseEvent): void {
31246
31237
  const raw = event as unknown as Record<string, unknown>;
31247
31238
  switch (event.type) {
@@ -31282,13 +31273,13 @@ export class ${w.className}AgentPanel {
31282
31273
  }
31283
31274
  `;
31284
31275
  }
31285
- function panelTemplateFile(w) {
31276
+ function panelTemplateFile(weaver) {
31286
31277
  return `<div class="flex h-full flex-col gap-3" (focusin)="refreshOffered()">
31287
31278
  <p
31288
31279
  class="shrink-0 rounded-md border border-border bg-surface-raised px-3 py-2 text-xs text-content-muted"
31289
31280
  >
31290
31281
  This is a stand-in, not an assistant. It speaks the protocol so you can watch the whole path;
31291
- replace <code class="text-content">${w.id}-agent-source.ts</code> with your own transport.
31282
+ replace <code class="text-content">${weaver.id}-agent-source.ts</code> with your own transport.
31292
31283
  </p>
31293
31284
 
31294
31285
  <div class="min-h-0 flex-1 overflow-auto">
@@ -31355,7 +31346,7 @@ function panelTemplateFile(w) {
31355
31346
  }
31356
31347
 
31357
31348
  // ../devkit/src/recipes/angular-weaver/agent-stand-in.ts
31358
- function standInFile(w) {
31349
+ function standInFile(weaver) {
31359
31350
  return `import { EventType, type BaseEvent, type Tool } from '@ag-ui/core';
31360
31351
 
31361
31352
  // WHAT THIS IS: a stand-in for an agent, and not an agent. It speaks the AG-UI protocol and nothing
@@ -31365,7 +31356,7 @@ function standInFile(w) {
31365
31356
  // WHAT REPLACES IT: this file, and only this file. Point askAgent at your own endpoint and yield the
31366
31357
  // events it streams back. The panel and the connection beside it stay exactly as they are.
31367
31358
 
31368
- const PACE = 40;
31359
+ const PAUSE_MS = 40;
31369
31360
 
31370
31361
  export interface AgentRequest {
31371
31362
  readonly runId: string;
@@ -31382,7 +31373,7 @@ export async function* askAgent(
31382
31373
  );
31383
31374
 
31384
31375
  yield event(EventType.RUN_STARTED, {
31385
- threadId: '${w.id}-stand-in',
31376
+ threadId: '${weaver.id}-stand-in',
31386
31377
  runId: request.runId,
31387
31378
  });
31388
31379
  yield* speak(
@@ -31410,7 +31401,7 @@ export async function* askAgent(
31410
31401
  }
31411
31402
 
31412
31403
  yield event(EventType.RUN_FINISHED, {
31413
- threadId: '${w.id}-stand-in',
31404
+ threadId: '${weaver.id}-stand-in',
31414
31405
  runId: request.runId,
31415
31406
  });
31416
31407
  }
@@ -31452,15 +31443,14 @@ function event(type: EventType, fields: Record<string, unknown>): BaseEvent {
31452
31443
  }
31453
31444
 
31454
31445
  function pause(): Promise<void> {
31455
- return new Promise((done) => setTimeout(done, PACE));
31446
+ return new Promise((done) => setTimeout(done, PAUSE_MS));
31456
31447
  }
31457
31448
  `;
31458
31449
  }
31459
31450
 
31460
31451
  // ../devkit/src/recipes/angular-weaver/agent-files.ts
31461
- var AG_UI_ADAPTER_VERSION = "0.14.1";
31462
31452
  var AG_UI_PROTOCOL_VERSION = "0.0.x";
31463
- function connectionFile(w) {
31453
+ function connectionFile(weaver) {
31464
31454
  return `import { signal } from '@angular/core';
31465
31455
  import {
31466
31456
  commandTools,
@@ -31472,13 +31462,13 @@ import type { PluginContext } from '@loomweaver/plugin-sdk';
31472
31462
 
31473
31463
  // A factory, not a module-level connection: everything a run needs lives in the closure, so a second
31474
31464
  // one never shares state with the first.
31475
- export function ${w.propertyName}Connection(ctx: PluginContext): CommandTools {
31465
+ export function ${weaver.propertyName}Connection(ctx: PluginContext): CommandTools {
31476
31466
  return commandTools(ctx, { before: (call) => decide(ctx, call) });
31477
31467
  }
31478
31468
 
31479
31469
  // The one connection this plugin activates, published for its panel. Set in activate(), cleared in
31480
31470
  // deactivate(), so the panel renders an honest empty state either side of that.
31481
- export const ${w.propertyName}Agent = signal<CommandTools | null>(null);
31471
+ export const ${weaver.propertyName}Agent = signal<CommandTools | null>(null);
31482
31472
 
31483
31473
  // What an agent's word is enough for is the command's own statement, declared where the command is
31484
31474
  // registered and read off the call here. No list of ids lives beside the commands: a list drifts from
@@ -31498,10 +31488,10 @@ async function decide(
31498
31488
  return { decision: 'run' };
31499
31489
  }
31500
31490
  const yes = await ctx.ui.confirm({
31501
- title: '${w.id}.agent.confirm.title',
31502
- message: '${w.id}.agent.confirm.message',
31503
- confirmLabel: '${w.id}.agent.confirm.yes',
31504
- cancelLabel: '${w.id}.agent.confirm.no',
31491
+ title: '${weaver.id}.agent.confirm.title',
31492
+ message: '${weaver.id}.agent.confirm.message',
31493
+ confirmLabel: '${weaver.id}.agent.confirm.yes',
31494
+ cancelLabel: '${weaver.id}.agent.confirm.no',
31505
31495
  tone: 'warning',
31506
31496
  });
31507
31497
  // A decision can only narrow. Letting a call through does not make it reachable: the workbench
@@ -31512,10 +31502,10 @@ async function decide(
31512
31502
  }
31513
31503
  `;
31514
31504
  }
31515
- function specFile(w) {
31505
+ function specFile(weaver) {
31516
31506
  return `import { EventType, type BaseEvent } from '@ag-ui/core';
31517
31507
  import type { CommandArguments, PluginContext } from '@loomweaver/plugin-sdk';
31518
- import { ${w.propertyName}Connection } from './${w.id}-agent';
31508
+ import { ${weaver.propertyName}Connection } from './${weaver.id}-agent';
31519
31509
 
31520
31510
  interface Asked {
31521
31511
  readonly id: string;
@@ -31526,7 +31516,7 @@ function contextThat(confirms: boolean, ran: Asked[]): PluginContext {
31526
31516
  return {
31527
31517
  invocableCommands: () => [
31528
31518
  // agentConsent travels with the command, which is what the connection reads off the call.
31529
- { id: '${w.id}.hello', title: '${w.name} action', description: 'Shows a short message.', agentConsent: 'ask' },
31519
+ { id: '${weaver.id}.hello', title: '${weaver.name} action', description: 'Shows a short message.', agentConsent: 'ask' },
31530
31520
  ],
31531
31521
  invokeCommand: (id: string, args?: CommandArguments) => {
31532
31522
  ran.push({ id, args });
@@ -31540,21 +31530,21 @@ function event(type: EventType, fields: Record<string, unknown>): BaseEvent {
31540
31530
  return { type, ...fields } as unknown as BaseEvent;
31541
31531
  }
31542
31532
 
31543
- describe('${w.propertyName}Connection', () => {
31533
+ describe('${weaver.propertyName}Connection', () => {
31544
31534
  it('offers what the workbench offers', () => {
31545
- const tools = ${w.propertyName}Connection(contextThat(true, []));
31546
- expect(tools.list().map((tool) => tool.name)).toEqual(['${w.id}.hello']);
31535
+ const tools = ${weaver.propertyName}Connection(contextThat(true, []));
31536
+ expect(tools.list().map((tool) => tool.name)).toEqual(['${weaver.id}.hello']);
31547
31537
  });
31548
31538
 
31549
31539
  it('assembles a call from its events and answers with the outcome', async () => {
31550
31540
  const ran: Asked[] = [];
31551
- const tools = ${w.propertyName}Connection(contextThat(true, ran));
31541
+ const tools = ${weaver.propertyName}Connection(contextThat(true, ran));
31552
31542
 
31553
31543
  expect(
31554
31544
  await tools.receive(
31555
31545
  event(EventType.TOOL_CALL_START, {
31556
31546
  toolCallId: 'c1',
31557
- toolCallName: '${w.id}.hello',
31547
+ toolCallName: '${weaver.id}.hello',
31558
31548
  }),
31559
31549
  ),
31560
31550
  ).toBeNull();
@@ -31568,19 +31558,19 @@ describe('${w.propertyName}Connection', () => {
31568
31558
  event(EventType.TOOL_CALL_END, { toolCallId: 'c1' }),
31569
31559
  );
31570
31560
 
31571
- expect(ran).toEqual([{ id: '${w.id}.hello', args: { tone: 'success' } }]);
31561
+ expect(ran).toEqual([{ id: '${weaver.id}.hello', args: { tone: 'success' } }]);
31572
31562
  expect(JSON.parse(answer?.content ?? '{}').tone).toBe('success');
31573
31563
  expect(answer?.error).toBeUndefined();
31574
31564
  });
31575
31565
 
31576
31566
  it('never reaches the workbench when a consequential call is declined', async () => {
31577
31567
  const ran: Asked[] = [];
31578
- const tools = ${w.propertyName}Connection(contextThat(false, ran));
31568
+ const tools = ${weaver.propertyName}Connection(contextThat(false, ran));
31579
31569
 
31580
31570
  await tools.receive(
31581
31571
  event(EventType.TOOL_CALL_START, {
31582
31572
  toolCallId: 'c2',
31583
- toolCallName: '${w.id}.hello',
31573
+ toolCallName: '${weaver.id}.hello',
31584
31574
  }),
31585
31575
  );
31586
31576
  await tools.receive(
@@ -31596,43 +31586,520 @@ describe('${w.propertyName}Connection', () => {
31596
31586
  });
31597
31587
  `;
31598
31588
  }
31599
- function agentSurfaceBlock(w) {
31589
+ function agentSurfaceBlock(weaver) {
31600
31590
  return [
31601
31591
  " ctx.registerSurface({",
31602
- ` id: '${w.id}.agent',`,
31603
- ` title: '${w.id}.agent.title',`,
31604
- ` icon: '${w.id}',`,
31605
- " docks: ['right-panel'],",
31592
+ ` id: '${weaver.id}.agent',`,
31593
+ ` title: '${weaver.id}.agent.title',`,
31594
+ ` icon: '${weaver.id}',`,
31595
+ ` docks: ['${RIGHT_PANEL_REGION}'],`,
31606
31596
  " padded: true,",
31607
- ` component: ${w.className}AgentPanel,`,
31597
+ ` component: ${weaver.className}AgentPanel,`,
31608
31598
  " });"
31609
31599
  ].join("\n");
31610
31600
  }
31611
- function agentFiles(w) {
31601
+ function agentFiles(weaver) {
31612
31602
  const files = {
31613
- [`src/lib/agent/${w.id}-agent.ts`]: connectionFile(w),
31614
- [`src/lib/agent/${w.id}-agent-source.ts`]: standInFile(w),
31615
- [`src/lib/agent/${w.id}-agent-panel.ts`]: panelFile(w),
31616
- [`src/lib/agent/${w.id}-agent-panel.html`]: panelTemplateFile(w)
31603
+ [`src/lib/agent/${weaver.id}-agent.ts`]: connectionFile(weaver),
31604
+ [`src/lib/agent/${weaver.id}-agent-source.ts`]: standInFile(weaver),
31605
+ [`src/lib/agent/${weaver.id}-agent-panel.ts`]: panelFile(weaver),
31606
+ [`src/lib/agent/${weaver.id}-agent-panel.html`]: panelTemplateFile(weaver)
31617
31607
  };
31618
- if (w.features.spec) {
31619
- files[`src/lib/agent/${w.id}-agent.spec.ts`] = specFile(w);
31608
+ if (weaver.features.spec) {
31609
+ files[`src/lib/agent/${weaver.id}-agent.spec.ts`] = specFile(weaver);
31620
31610
  }
31621
31611
  return files;
31622
31612
  }
31623
31613
 
31624
- // ../devkit/src/recipes/angular-weaver/weaver-terms.ts
31614
+ // ../devkit/src/recipes/angular-weaver/weaver-i18n.ts
31615
+ function i18nBundle(weaver) {
31616
+ const bundle = { title: weaver.name };
31617
+ if (weaver.features.container) {
31618
+ bundle["canvas"] = "Canvas";
31619
+ bundle["details"] = "Details";
31620
+ }
31621
+ if (weaver.features.command) {
31622
+ bundle["action"] = `${weaver.name} action`;
31623
+ bundle["actionDescription"] = `Shows a short ${weaver.name} message in the tone the caller chooses.`;
31624
+ bundle["actionTone"] = "How the message is shown: info, success or warning. Use info unless the caller asked for emphasis.";
31625
+ bundle["actionAnswers"] = "The tone the message was shown in.";
31626
+ }
31627
+ if (weaver.features.about) bundle["about"] = `About ${weaver.name}`;
31628
+ if (weaver.features.agent) {
31629
+ bundle["agent"] = {
31630
+ title: `${weaver.name} assistant`,
31631
+ confirm: {
31632
+ title: "Run this command?",
31633
+ message: "An agent asked to run a command that was marked consequential.",
31634
+ yes: "Run it",
31635
+ no: "Not now"
31636
+ }
31637
+ };
31638
+ }
31639
+ if (weaver.features.settings) {
31640
+ bundle["settings"] = { title: weaver.name, enabled: "Enabled", note: "Note" };
31641
+ }
31642
+ return bundle;
31643
+ }
31644
+ function i18nFile(weaver) {
31645
+ return JSON.stringify(i18nBundle(weaver), null, 2) + "\n";
31646
+ }
31647
+
31648
+ // ../devkit/src/lib/validate/manifest.ts
31649
+ var KNOWN_CAPABILITIES = [
31650
+ "contributions",
31651
+ "ui",
31652
+ "host",
31653
+ "navigation",
31654
+ "session",
31655
+ "theme",
31656
+ "automation"
31657
+ ];
31658
+ function validateManifest(manifest2, known = KNOWN_CAPABILITIES) {
31659
+ const findings = [];
31660
+ if (typeof manifest2.id !== "string" || !isKebabId(manifest2.id)) {
31661
+ findings.push({
31662
+ level: "error",
31663
+ code: "manifest.id",
31664
+ message: `Plugin id must be a kebab-case string; got ${JSON.stringify(manifest2.id)}.`,
31665
+ path: "manifest.id"
31666
+ });
31667
+ }
31668
+ if (manifest2.name !== void 0 && typeof manifest2.name !== "string") {
31669
+ findings.push({
31670
+ level: "error",
31671
+ code: "manifest.name",
31672
+ message: "Plugin name must be a string when provided.",
31673
+ path: "manifest.name"
31674
+ });
31675
+ }
31676
+ findings.push(...validateCapabilities(manifest2.capabilities, known));
31677
+ return findings;
31678
+ }
31679
+ function validateCapabilities(capabilities, known) {
31680
+ if (capabilities === void 0) {
31681
+ return [];
31682
+ }
31683
+ if (!Array.isArray(capabilities)) {
31684
+ return [
31685
+ {
31686
+ level: "error",
31687
+ code: "manifest.capabilities",
31688
+ message: "capabilities must be an array when provided.",
31689
+ path: "manifest.capabilities"
31690
+ }
31691
+ ];
31692
+ }
31693
+ const findings = [];
31694
+ const seen = /* @__PURE__ */ new Set();
31695
+ for (const capability of capabilities) {
31696
+ if (typeof capability !== "string" || !known.includes(capability)) {
31697
+ findings.push({
31698
+ level: "error",
31699
+ code: "manifest.capability.unknown",
31700
+ message: `Unknown capability ${JSON.stringify(capability)}. Known: ${known.join(", ")}.`,
31701
+ path: "manifest.capabilities"
31702
+ });
31703
+ continue;
31704
+ }
31705
+ if (seen.has(capability)) {
31706
+ findings.push({
31707
+ level: "warning",
31708
+ code: "manifest.capability.duplicate",
31709
+ message: `Duplicate capability "${capability}".`,
31710
+ path: "manifest.capabilities"
31711
+ });
31712
+ }
31713
+ seen.add(capability);
31714
+ }
31715
+ return findings;
31716
+ }
31717
+
31718
+ // ../devkit/src/recipes/angular-weaver/weaver-input.ts
31719
+ var DEFAULT_MENU_SLOT = "content/tab/context";
31720
+ function accessLiteral(spec) {
31721
+ if (spec === "authenticated") {
31722
+ return "{ authenticated: true }";
31723
+ }
31724
+ if (spec === "anonymous") {
31725
+ return "{ authenticated: false }";
31726
+ }
31727
+ if (spec.startsWith("role:")) {
31728
+ const role = spec.slice("role:".length);
31729
+ if (!role) {
31730
+ throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
31731
+ }
31732
+ if (/['\\]/.test(role)) {
31733
+ throw new Error(
31734
+ `Access role "${role}" must not contain quotes or backslashes.`
31735
+ );
31736
+ }
31737
+ return `{ anyRole: ['${role}'] }`;
31738
+ }
31739
+ throw new Error(
31740
+ `Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
31741
+ );
31742
+ }
31743
+ function resolveMenuSlot(menu) {
31744
+ if (menu === true) {
31745
+ return DEFAULT_MENU_SLOT;
31746
+ }
31747
+ return typeof menu === "string" && menu.length ? menu : void 0;
31748
+ }
31749
+ var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
31750
+ "cmd",
31751
+ "command",
31752
+ "ctrl",
31753
+ "control",
31754
+ "meta"
31755
+ ]);
31756
+ function assertPlatformNeutralChord(shortcut) {
31757
+ const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
31758
+ const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
31759
+ if (bound) {
31760
+ throw new Error(
31761
+ `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.`
31762
+ );
31763
+ }
31764
+ }
31765
+ function resolveFeatures(id, input) {
31766
+ const menuSlot = resolveMenuSlot(input?.menu);
31767
+ const barItem = Boolean(input?.barItem);
31768
+ const hasShortcut = Boolean(input?.shortcut?.trim());
31769
+ if (hasShortcut) {
31770
+ assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
31771
+ }
31772
+ const instanceable = Boolean(input?.instanceable);
31773
+ const container = Boolean(input?.container);
31774
+ if (instanceable && container) {
31775
+ throw new Error(
31776
+ '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.'
31777
+ );
31778
+ }
31779
+ const agent = Boolean(input?.agent);
31780
+ return {
31781
+ command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
31782
+ menuSlot,
31783
+ settings: Boolean(input?.settings),
31784
+ access: input?.access ? accessLiteral(input.access) : void 0,
31785
+ shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
31786
+ barItem,
31787
+ about: Boolean(input?.about),
31788
+ instanceable,
31789
+ container,
31790
+ agent,
31791
+ spec: input?.spec !== false
31792
+ };
31793
+ }
31794
+ function deriveCapabilities(features) {
31795
+ const set2 = /* @__PURE__ */ new Set(["contributions", "navigation"]);
31796
+ if (features.command) set2.add("ui");
31797
+ if (features.about) {
31798
+ set2.add("ui");
31799
+ set2.add("host");
31800
+ }
31801
+ if (features.agent) {
31802
+ set2.add("ui");
31803
+ set2.add("automation");
31804
+ }
31805
+ return KNOWN_CAPABILITIES.filter((capability) => set2.has(capability));
31806
+ }
31807
+ function resolveWeaverInput(input) {
31808
+ if (!isKebabId(input.id)) {
31809
+ throw new Error(
31810
+ `Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
31811
+ );
31812
+ }
31813
+ const features = resolveFeatures(input.id, input.features);
31814
+ const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
31815
+ return {
31816
+ id: input.id,
31817
+ name: input.name?.trim() || toTitleCase(input.id),
31818
+ className: toPascalCase(input.id),
31819
+ propertyName: toCamelCase(input.id),
31820
+ capabilities,
31821
+ features,
31822
+ prefix: input.prefix?.trim() || "lw",
31823
+ importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
31824
+ };
31825
+ }
31826
+
31827
+ // ../devkit/src/recipes/angular-weaver/weaver-command.ts
31828
+ function commandBlock(weaver) {
31829
+ return [
31830
+ " ctx.registerCommand({",
31831
+ ` id: '${weaver.id}.hello',`,
31832
+ ` title: '${weaver.id}.action',`,
31833
+ ` description: '${weaver.id}.actionDescription',`,
31834
+ " arguments: [",
31835
+ ` { name: 'tone', kind: 'choice', choices: ['info', 'success', 'warning'], description: '${weaver.id}.actionTone' },`,
31836
+ " ],",
31837
+ ` answers: '${weaver.id}.actionAnswers',`,
31838
+ ` shortcut: '${weaver.features.shortcut}',`,
31839
+ " callable: true,",
31840
+ ...weaver.features.agent ? [
31841
+ " // What an agent's word is enough for. The platform states it and enforces nothing:",
31842
+ " // the asking is the connection's half, which reads this off the call.",
31843
+ " agentConsent: 'ask',"
31844
+ ] : [],
31845
+ " run: (_context, args) => {",
31846
+ " const tone = toneOf(args?.['tone']);",
31847
+ ` ctx.ui.toast({ message: '${weaver.id}.action', kind: tone });`,
31848
+ " return { tone };",
31849
+ " },",
31850
+ " });"
31851
+ ].join("\n");
31852
+ }
31853
+ function toneHelper() {
31854
+ return [
31855
+ "type Tone = 'info' | 'success' | 'warning';",
31856
+ "",
31857
+ "// The workbench already refused anything outside the declared choices; this narrows the type.",
31858
+ "function toneOf(value: unknown): Tone {",
31859
+ " return value === 'success' || value === 'warning' ? value : 'info';",
31860
+ "}"
31861
+ ].join("\n");
31862
+ }
31863
+
31864
+ // ../devkit/src/recipes/angular-weaver/weaver-plugin.ts
31625
31865
  var CONTAINER_EXAMPLE_ID = "example";
31626
- function capabilityItems(capabilities) {
31627
- return capabilities.map((capability) => `'${capability}'`).join(", ");
31866
+ 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>';
31867
+ function containerChildIds(weaver) {
31868
+ return [`${weaver.id}.canvas`, `${weaver.id}.details`];
31869
+ }
31870
+ function containerSurfaceBlock(weaver) {
31871
+ const children = containerChildIds(weaver).map((id) => `'${id}'`).join(", ");
31872
+ const lines = [
31873
+ " ctx.registerSurface({",
31874
+ ` id: '${weaver.id}',`,
31875
+ ` title: '${weaver.id}.title',`,
31876
+ ` icon: '${weaver.id}',`,
31877
+ ` routable: { path: '${weaver.id}/:id' },`,
31878
+ " container: {",
31879
+ ` children: [${children}],`,
31880
+ ` initial: [${children}],`,
31881
+ " },",
31882
+ ...accessLine(weaver),
31883
+ " });"
31884
+ ];
31885
+ for (const [suffix, className] of [
31886
+ ["canvas", `${weaver.className}CanvasView`],
31887
+ ["details", `${weaver.className}DetailsView`]
31888
+ ]) {
31889
+ lines.push(
31890
+ " ctx.registerSurface({",
31891
+ ` id: '${weaver.id}.${suffix}',`,
31892
+ ` title: '${weaver.id}.${suffix}',`,
31893
+ " docks: [],",
31894
+ ` component: ${className},`,
31895
+ " });"
31896
+ );
31897
+ }
31898
+ return lines.join("\n");
31899
+ }
31900
+ function surfaceBlock(weaver) {
31901
+ if (weaver.features.container) {
31902
+ return containerSurfaceBlock(weaver);
31903
+ }
31904
+ const lines = [
31905
+ " ctx.registerSurface({",
31906
+ ` id: '${weaver.id}',`,
31907
+ ` title: '${weaver.id}.title',`,
31908
+ ` icon: '${weaver.id}',`,
31909
+ ` component: ${weaver.className}View,`
31910
+ ];
31911
+ if (weaver.features.instanceable) {
31912
+ lines.push(` docks: ['${LEFT_PANEL_REGION}'],`, " instanceable: true,");
31913
+ } else {
31914
+ lines.push(` routable: { path: '${weaver.id}' },`);
31915
+ }
31916
+ lines.push(...accessLine(weaver), " });");
31917
+ return lines.join("\n");
31918
+ }
31919
+ function railTarget(weaver) {
31920
+ if (weaver.features.container) {
31921
+ return `ctx.navigateContent('${weaver.id}/${CONTAINER_EXAMPLE_ID}')`;
31922
+ }
31923
+ if (weaver.features.instanceable) {
31924
+ return `ctx.revealSurface('${weaver.id}')`;
31925
+ }
31926
+ return `ctx.navigateContent('${weaver.id}')`;
31927
+ }
31928
+ function railBlock(weaver) {
31929
+ const lines = [
31930
+ " ctx.registerRailItem({",
31931
+ ` id: '${weaver.id}.rail',`,
31932
+ ` rail: '${RAIL_REGION}',`,
31933
+ ` icon: '${weaver.id}',`,
31934
+ ` title: '${weaver.id}.title',`,
31935
+ ` run: () => ${railTarget(weaver)},`,
31936
+ ...accessLine(weaver),
31937
+ " });"
31938
+ ];
31939
+ return lines.join("\n");
31940
+ }
31941
+ function barItemBlock(weaver) {
31942
+ return [
31943
+ " ctx.registerBarItem({",
31944
+ ` id: '${weaver.id}.bar',`,
31945
+ ` bar: '${STATUS_BAR_REGION}',`,
31946
+ " slot: 'end',",
31947
+ ` icon: '${weaver.id}',`,
31948
+ ` tooltip: '${weaver.id}.action',`,
31949
+ ` command: '${weaver.id}.hello',`,
31950
+ " });"
31951
+ ].join("\n");
31952
+ }
31953
+ function aboutCommandBlock(weaver) {
31954
+ return [
31955
+ " ctx.registerCommand({",
31956
+ ` id: '${weaver.id}.about',`,
31957
+ ` title: '${weaver.id}.about',`,
31958
+ ` run: () => ctx.ui.open(${weaver.className}AboutDialog, { data: ctx.host, title: '${weaver.id}.title' }),`,
31959
+ " });"
31960
+ ].join("\n");
31961
+ }
31962
+ function aboutRailBlock(weaver) {
31963
+ return [
31964
+ " ctx.registerRailItem({",
31965
+ ` id: '${weaver.id}.rail.about',`,
31966
+ ` rail: '${RAIL_REGION}',`,
31967
+ " anchor: 'bottom',",
31968
+ ` icon: '${weaver.id}',`,
31969
+ ` title: '${weaver.id}.about',`,
31970
+ ` command: '${weaver.id}.about',`,
31971
+ " });"
31972
+ ].join("\n");
31973
+ }
31974
+ function menuBlock(weaver) {
31975
+ return [
31976
+ " ctx.registerMenuItem({",
31977
+ ` menu: '${weaver.features.menuSlot}',`,
31978
+ ` command: '${weaver.id}.hello',`,
31979
+ " });"
31980
+ ].join("\n");
31981
+ }
31982
+ function settingsBlock(weaver) {
31983
+ return [
31984
+ " ctx.registerSettingsSection({",
31985
+ ` id: '${weaver.id}',`,
31986
+ ` title: '${weaver.id}.settings.title',`,
31987
+ " rows: [",
31988
+ " {",
31989
+ " id: 'enabled',",
31990
+ ` label: '${weaver.id}.settings.enabled',`,
31991
+ ` control: { kind: 'toggle', value: () => ${weaver.propertyName}Enabled(), set: (v) => ${weaver.propertyName}Enabled.set(v) },`,
31992
+ " },",
31993
+ " {",
31994
+ " id: 'note',",
31995
+ ` label: '${weaver.id}.settings.note',`,
31996
+ ` control: { kind: 'text', value: () => ${weaver.propertyName}Note(), set: (v) => ${weaver.propertyName}Note.set(v) },`,
31997
+ " },",
31998
+ " ],",
31999
+ " });"
32000
+ ].join("\n");
32001
+ }
32002
+ function pluginImports(weaver) {
32003
+ const imports = ["import { Plugin } from '@loomweaver/plugin-sdk';"];
32004
+ if (weaver.features.container) {
32005
+ imports.push(
32006
+ `import { ${weaver.className}CanvasView } from '../views/${weaver.id}-canvas-view';`,
32007
+ `import { ${weaver.className}DetailsView } from '../views/${weaver.id}-details-view';`
32008
+ );
32009
+ } else {
32010
+ imports.push(`import { ${weaver.className}View } from '../views/${weaver.id}-view';`);
32011
+ }
32012
+ if (weaver.features.about) {
32013
+ imports.push(
32014
+ `import { ${weaver.className}AboutDialog } from '../dialogs/${weaver.id}-about-dialog';`
32015
+ );
32016
+ }
32017
+ if (weaver.features.agent) {
32018
+ imports.push(
32019
+ `import { ${weaver.propertyName}Agent, ${weaver.propertyName}Connection } from '../agent/${weaver.id}-agent';`,
32020
+ `import { ${weaver.className}AgentPanel } from '../agent/${weaver.id}-agent-panel';`
32021
+ );
32022
+ }
32023
+ if (weaver.features.settings) {
32024
+ imports.unshift("import { signal } from '@angular/core';");
32025
+ }
32026
+ return imports;
32027
+ }
32028
+ function pluginConsts(weaver) {
32029
+ const consts = [`const icon =
32030
+ '${SURFACE_ICON}';`];
32031
+ if (weaver.features.settings) {
32032
+ consts.push(
32033
+ `const ${weaver.propertyName}Enabled = signal(true);`,
32034
+ `const ${weaver.propertyName}Note = signal('');`
32035
+ );
32036
+ }
32037
+ if (weaver.features.command) consts.push(toneHelper());
32038
+ return consts;
32039
+ }
32040
+ function pluginBody(weaver) {
32041
+ const body = [` ctx.contributeIcons({ '${weaver.id}': icon });`];
32042
+ if (weaver.features.agent) {
32043
+ body.push(
32044
+ ` ${weaver.propertyName}Agent.set(${weaver.propertyName}Connection(ctx));`
32045
+ );
32046
+ }
32047
+ if (weaver.features.command) body.push(commandBlock(weaver));
32048
+ if (weaver.features.about) body.push(aboutCommandBlock(weaver));
32049
+ body.push(surfaceBlock(weaver), railBlock(weaver));
32050
+ if (weaver.features.about) body.push(aboutRailBlock(weaver));
32051
+ if (weaver.features.barItem) body.push(barItemBlock(weaver));
32052
+ if (weaver.features.menuSlot) body.push(menuBlock(weaver));
32053
+ if (weaver.features.settings) body.push(settingsBlock(weaver));
32054
+ if (weaver.features.agent) body.push(agentSurfaceBlock(weaver));
32055
+ return body;
32056
+ }
32057
+ function pluginFile(weaver) {
32058
+ const body = pluginBody(weaver);
32059
+ const deactivate = weaver.features.agent ? `
32060
+ deactivate() {
32061
+ ${weaver.propertyName}Agent.set(null);
32062
+ },` : "";
32063
+ return `${pluginImports(weaver).join("\n")}
32064
+
32065
+ ${pluginConsts(weaver).join("\n\n")}
32066
+
32067
+ export const ${weaver.propertyName}Plugin: Plugin = {
32068
+ manifest: {
32069
+ id: '${weaver.id}',
32070
+ name: '${weaver.name}',
32071
+ capabilities: [${quotedList(weaver.capabilities)}],
32072
+ },
32073
+ activate(ctx) {
32074
+ ${body.join("\n")}
32075
+ },${deactivate}
32076
+ };
32077
+ `;
32078
+ }
32079
+ function accessLine(weaver) {
32080
+ return weaver.features.access ? [` access: ${weaver.features.access},`] : [];
32081
+ }
32082
+
32083
+ // ../devkit/src/recipes/platform-version.ts
32084
+ var PLATFORM_VERSION = "0.14.2";
32085
+
32086
+ // ../devkit/src/recipes/readme-notes.ts
32087
+ var UNTAGGED_NOTE = [
32088
+ "The project is generated **untagged**: Nx tags belong to your `depConstraints`, and inventing",
32089
+ "one would fail a lint policy you never opted this project into. If your workspace enforces",
32090
+ "module boundaries, give it tags your constraints allow \u2014 `--tags` at generation time, or",
32091
+ "`tags` in `project.json` afterwards."
32092
+ ];
32093
+ function asBullet(lines) {
32094
+ return lines.map((line, index) => index === 0 ? `- ${line}` : ` ${line}`);
31628
32095
  }
31629
32096
 
31630
32097
  // ../devkit/src/recipes/angular-weaver/weaver-readme.ts
31631
- function surfaceNotes(w) {
31632
- const railNote = "Rail and bar items reference region ids (`primary`, `status`) that must exist in your layout.";
31633
- if (w.features.container) {
32098
+ function surfaceNotes(weaver) {
32099
+ const railNote = "Rail and bar items reference region ids (`primary` rail, `status-bar` bar) that must exist in your layout.";
32100
+ if (weaver.features.container) {
31634
32101
  return [
31635
- `The surface is a **container**: it is routable at \`/${w.id}/:id\`, and its tab holds a`,
32102
+ `The surface is a **container**: it is routable at \`/${weaver.id}/:id\`, and its tab holds a`,
31636
32103
  "nested pane tree of child surfaces. The host draws the inner tabs, splits and drag targets; this",
31637
32104
  "weaver only declares which children it offers.",
31638
32105
  "",
@@ -31652,9 +32119,9 @@ function surfaceNotes(w) {
31652
32119
  railNote
31653
32120
  ];
31654
32121
  }
31655
- if (w.features.instanceable) {
32122
+ if (weaver.features.instanceable) {
31656
32123
  return [
31657
- `The surface is **docked** into the \`primary\` region and marked \`instanceable\`, so the host shows a`,
32124
+ `The surface is **docked** into the \`left-panel\` region and marked \`instanceable\`, so the host shows a`,
31658
32125
  "switcher for saving, naming, renaming and deleting several configurations of it, each with its own",
31659
32126
  "`VIEW_STATE` blob.",
31660
32127
  "",
@@ -31672,7 +32139,7 @@ function surfaceNotes(w) {
31672
32139
  ];
31673
32140
  }
31674
32141
  return [
31675
- `The surface is routable at \`/${w.id}\`; ${railNote.charAt(0).toLowerCase()}${railNote.slice(1)}`,
32142
+ `The surface is routable at \`/${weaver.id}\`; ${railNote.charAt(0).toLowerCase()}${railNote.slice(1)}`,
31676
32143
  "",
31677
32144
  "A routable surface has **no `VIEW_STATE` handle** \u2014 injecting the token there throws. It owns a URL,",
31678
32145
  "so anything shareable (a filter, the active sub-tab) belongs in route params or `subRoutes`, where it",
@@ -31681,18 +32148,18 @@ function surfaceNotes(w) {
31681
32148
  "flavour instead."
31682
32149
  ];
31683
32150
  }
31684
- function agentNotes(w) {
32151
+ function agentNotes(weaver) {
31685
32152
  return [
31686
32153
  "",
31687
32154
  "## The agent connection",
31688
32155
  "",
31689
32156
  `\`src/lib/agent/\` holds three files and one of them is meant to be thrown away.`,
31690
32157
  "",
31691
- `- \`${w.id}-agent.ts\` is the connection: the workbench's own commands offered as tools, and a`,
32158
+ `- \`${weaver.id}-agent.ts\` is the connection: the workbench's own commands offered as tools, and a`,
31692
32159
  " seam where this weaver decides about a call before it runs. Nothing is registered twice \u2014 the",
31693
32160
  " list comes from the workbench, already narrowed by everything that would refuse the call.",
31694
- `- \`${w.id}-agent-panel.ts\` shows what is offered, the call as it streams, and the outcome.`,
31695
- `- \`${w.id}-agent-source.ts\` is a **stand-in**, not an assistant: it produces the protocol's own`,
32161
+ `- \`${weaver.id}-agent-panel.ts\` shows what is offered, the call as it streams, and the outcome.`,
32162
+ `- \`${weaver.id}-agent-source.ts\` is a **stand-in**, not an assistant: it produces the protocol's own`,
31696
32163
  " events so the whole path runs before you have connected anything. Replace that one file with",
31697
32164
  " your transport and nothing else changes. No transport, credential or model is generated for",
31698
32165
  " you, because none of them can be guessed.",
@@ -31705,15 +32172,15 @@ function agentNotes(w) {
31705
32172
  `The generated weaver needs two packages your project may not carry yet:`,
31706
32173
  "",
31707
32174
  "```bash",
31708
- `npm i @loomweaver/ag-ui @ag-ui/core`,
32175
+ `npm i @loomweaver/ag-ui@^${PLATFORM_VERSION} @ag-ui/core@${AG_UI_PROTOCOL_VERSION}`,
31709
32176
  "```",
31710
32177
  "",
31711
32178
  "The Nx generator and the CLI record them for you; the MCP route names them instead."
31712
32179
  ];
31713
32180
  }
31714
- function readmeFile(w) {
32181
+ function readmeFile(weaver) {
31715
32182
  return [
31716
- `# ${w.name} weaver`,
32183
+ `# ${weaver.name} weaver`,
31717
32184
  "",
31718
32185
  `A LoomWeaver weaver (a domain plugin bundle). It consumes only the public \`@loomweaver/plugin-sdk\` contract.`,
31719
32186
  "",
@@ -31723,22 +32190,22 @@ function readmeFile(w) {
31723
32190
  ` returns an array, so spread it:`,
31724
32191
  "",
31725
32192
  " ```ts",
31726
- ` import { ${w.propertyName}Plugin } from '${w.importPath}'; // Nx: the workspace alias; without one, a relative path to this library's src/index.ts`,
31727
- ` ...providePlugins(${w.propertyName}Plugin),`,
32193
+ ` import { ${weaver.propertyName}Plugin } from '${weaver.importPath}'; // Nx: the workspace alias; without one, a relative path to this library's src/index.ts`,
32194
+ ` ...providePlugins(${weaver.propertyName}Plugin),`,
31728
32195
  " ```",
31729
32196
  "",
31730
32197
  `2. Grant its capabilities (default-deny) via \`provideCapabilityGrants\`:`,
31731
32198
  "",
31732
32199
  " ```ts",
31733
- ` provideCapabilityGrants({ '${w.id}': [${capabilityItems(w.capabilities)}] });`,
32200
+ ` provideCapabilityGrants({ '${weaver.id}': [${quotedList(weaver.capabilities)}] });`,
31734
32201
  " ```",
31735
32202
  "",
31736
- `3. Compose its translations with \`provideTranslationNamespaces('${w.id}')\` \u2014 and serve the`,
32203
+ `3. Compose its translations with \`provideTranslationNamespaces('${weaver.id}')\` \u2014 and serve the`,
31737
32204
  ` bundle by adding an assets glob to your application's build target, so the loader can fetch`,
31738
- ` \`/i18n/${w.id}/<lang>.json\` (the Nx generator adds this glob for you):`,
32205
+ ` \`/i18n/${weaver.id}/<lang>.json\` (the Nx generator adds this glob for you):`,
31739
32206
  "",
31740
32207
  " ```json",
31741
- ` { "glob": "**/*.json", "input": "<path to this library>/src/lib/i18n", "output": "i18n/${w.id}" }`,
32208
+ ` { "glob": "**/*.json", "input": "<path to this library>/src/lib/i18n", "output": "i18n/${weaver.id}" }`,
31742
32209
  " ```",
31743
32210
  "",
31744
32211
  `4. If your application compiles the shell's theme with Tailwind, name this library as a source`,
@@ -31751,459 +32218,65 @@ function readmeFile(w) {
31751
32218
  ` @source '<path from that stylesheet to this library>/src';`,
31752
32219
  " ```",
31753
32220
  "",
31754
- ...surfaceNotes(w),
31755
- ...w.features.agent ? agentNotes(w) : [],
32221
+ ...surfaceNotes(weaver),
32222
+ ...weaver.features.agent ? agentNotes(weaver) : [],
31756
32223
  "",
31757
32224
  "## After scaffolding",
31758
32225
  "",
31759
32226
  "- `src/lib/i18n/de.json` starts as a copy of the English strings \u2014 translate it.",
31760
32227
  `- A scaffolded command defaults its shortcut to \`mod+shift+<first letter of the id>\` \u2014 two weavers whose ids share a first letter collide; pass \`--shortcut\` or edit the command.`,
31761
- "- The project is generated **untagged**: Nx tags belong to your `depConstraints`, and inventing",
31762
- " one would fail a lint policy you never opted this project into. If your workspace enforces",
31763
- " module boundaries, give it tags your constraints allow \u2014 `--tags` at generation time, or",
31764
- " `tags` in `project.json` afterwards.",
32228
+ ...asBullet(UNTAGGED_NOTE),
31765
32229
  ""
31766
32230
  ].join("\n");
31767
32231
  }
31768
32232
 
31769
- // ../devkit/src/recipes/angular-weaver/weaver-i18n.ts
31770
- function i18nBundle(w) {
31771
- const bundle = { title: w.name };
31772
- if (w.features.container) {
31773
- bundle["canvas"] = "Canvas";
31774
- bundle["details"] = "Details";
31775
- }
31776
- if (w.features.command) {
31777
- bundle["action"] = `${w.name} action`;
31778
- bundle["actionDescription"] = `Shows a short ${w.name} message in the tone the caller chooses.`;
31779
- bundle["actionTone"] = "How the message is shown: info, success or warning. Use info unless the caller asked for emphasis.";
31780
- bundle["actionAnswers"] = "The tone the message was shown in.";
31781
- }
31782
- if (w.features.about) bundle["about"] = `About ${w.name}`;
31783
- if (w.features.agent) {
31784
- bundle["agent"] = {
31785
- title: `${w.name} assistant`,
31786
- confirm: {
31787
- title: "Run this command?",
31788
- message: "An agent asked to run a command that was marked consequential.",
31789
- yes: "Run it",
31790
- no: "Not now"
31791
- }
31792
- };
31793
- }
31794
- if (w.features.settings)
31795
- bundle["settings"] = { title: w.name, enabled: "Enabled", note: "Note" };
31796
- return bundle;
31797
- }
31798
- function i18nFile(w) {
31799
- return JSON.stringify(i18nBundle(w), null, 2) + "\n";
31800
- }
31801
-
31802
- // ../devkit/src/recipes/angular-weaver/weaver-command.ts
31803
- function commandBlock(w) {
31804
- return [
31805
- " ctx.registerCommand({",
31806
- ` id: '${w.id}.hello',`,
31807
- ` title: '${w.id}.action',`,
31808
- ` description: '${w.id}.actionDescription',`,
31809
- " arguments: [",
31810
- ` { name: 'tone', kind: 'choice', choices: ['info', 'success', 'warning'], description: '${w.id}.actionTone' },`,
31811
- " ],",
31812
- ` answers: '${w.id}.actionAnswers',`,
31813
- ` shortcut: '${w.features.shortcut}',`,
31814
- " callable: true,",
31815
- ...w.features.agent ? [
31816
- " // What an agent's word is enough for. The platform states it and enforces nothing:",
31817
- " // the asking is the connection's half, which reads this off the call.",
31818
- " agentConsent: 'ask',"
31819
- ] : [],
31820
- " run: (_context, args) => {",
31821
- " const tone = toneOf(args?.['tone']);",
31822
- ` ctx.ui.toast({ message: '${w.id}.action', kind: tone });`,
31823
- " return { tone };",
31824
- " },",
31825
- " });"
31826
- ].join("\n");
31827
- }
31828
- function toneHelper() {
31829
- return [
31830
- "type Tone = 'info' | 'success' | 'warning';",
31831
- "",
31832
- "// The workbench already refused anything outside the declared choices; this narrows the type.",
31833
- "function toneOf(value: unknown): Tone {",
31834
- " return value === 'success' || value === 'warning' ? value : 'info';",
31835
- "}"
31836
- ].join("\n");
31837
- }
31838
-
31839
- // ../devkit/src/recipes/angular-weaver/weaver-plugin.ts
31840
- 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>';
31841
- function containerChildIds(w) {
31842
- return [`${w.id}.canvas`, `${w.id}.details`];
31843
- }
31844
- function containerSurfaceBlock(w) {
31845
- const children = containerChildIds(w).map((id) => `'${id}'`).join(", ");
31846
- const lines = [
31847
- " ctx.registerSurface({",
31848
- ` id: '${w.id}',`,
31849
- ` title: '${w.id}.title',`,
31850
- ` icon: '${w.id}',`,
31851
- ` routable: { path: '${w.id}/:id' },`,
31852
- " container: {",
31853
- ` children: [${children}],`,
31854
- ` initial: [${children}],`,
31855
- " },"
31856
- ];
31857
- if (w.features.access) lines.push(` access: ${w.features.access},`);
31858
- lines.push(" });");
31859
- for (const [suffix, className] of [
31860
- ["canvas", `${w.className}CanvasView`],
31861
- ["details", `${w.className}DetailsView`]
31862
- ]) {
31863
- lines.push(
31864
- " ctx.registerSurface({",
31865
- ` id: '${w.id}.${suffix}',`,
31866
- ` title: '${w.id}.${suffix}',`,
31867
- " docks: [],",
31868
- ` component: ${className},`,
31869
- " });"
31870
- );
31871
- }
31872
- return lines.join("\n");
31873
- }
31874
- function surfaceBlock(w) {
31875
- if (w.features.container) {
31876
- return containerSurfaceBlock(w);
31877
- }
31878
- const lines = [
31879
- " ctx.registerSurface({",
31880
- ` id: '${w.id}',`,
31881
- ` title: '${w.id}.title',`,
31882
- ` icon: '${w.id}',`,
31883
- ` component: ${w.className}View,`
31884
- ];
31885
- if (w.features.instanceable) {
31886
- lines.push(" docks: ['left-panel'],", " instanceable: true,");
31887
- } else {
31888
- lines.push(` routable: { path: '${w.id}' },`);
31889
- }
31890
- if (w.features.access) lines.push(` access: ${w.features.access},`);
31891
- lines.push(" });");
31892
- return lines.join("\n");
31893
- }
31894
- function railTarget(w) {
31895
- if (w.features.container) {
31896
- return `ctx.navigateContent('${w.id}/${CONTAINER_EXAMPLE_ID}')`;
31897
- }
31898
- if (w.features.instanceable) {
31899
- return `ctx.revealSurface('${w.id}')`;
31900
- }
31901
- return `ctx.navigateContent('${w.id}')`;
31902
- }
31903
- function railBlock(w) {
31904
- const lines = [
31905
- " ctx.registerRailItem({",
31906
- ` id: '${w.id}.rail',`,
31907
- " rail: 'primary',",
31908
- ` icon: '${w.id}',`,
31909
- ` title: '${w.id}.title',`,
31910
- ` run: () => ${railTarget(w)},`
31911
- ];
31912
- if (w.features.access) lines.push(` access: ${w.features.access},`);
31913
- lines.push(" });");
31914
- return lines.join("\n");
31915
- }
31916
- function barItemBlock(w) {
31917
- return [
31918
- " ctx.registerBarItem({",
31919
- ` id: '${w.id}.bar',`,
31920
- " bar: 'status-bar',",
31921
- " slot: 'end',",
31922
- ` icon: '${w.id}',`,
31923
- ` tooltip: '${w.id}.action',`,
31924
- ` command: '${w.id}.hello',`,
31925
- " });"
31926
- ].join("\n");
31927
- }
31928
- function aboutCommandBlock(w) {
31929
- return [
31930
- " ctx.registerCommand({",
31931
- ` id: '${w.id}.about',`,
31932
- ` title: '${w.id}.about',`,
31933
- ` run: () => ctx.ui.open(${w.className}AboutDialog, { data: ctx.host, title: '${w.id}.title' }),`,
31934
- " });"
31935
- ].join("\n");
31936
- }
31937
- function aboutRailBlock(w) {
31938
- return [
31939
- " ctx.registerRailItem({",
31940
- ` id: '${w.id}.rail.about',`,
31941
- " rail: 'primary',",
31942
- " anchor: 'bottom',",
31943
- ` icon: '${w.id}',`,
31944
- ` title: '${w.id}.about',`,
31945
- ` command: '${w.id}.about',`,
31946
- " });"
31947
- ].join("\n");
31948
- }
31949
- function menuBlock(w) {
31950
- return [
31951
- " ctx.registerMenuItem({",
31952
- ` menu: '${w.features.menuSlot}',`,
31953
- ` command: '${w.id}.hello',`,
31954
- " });"
31955
- ].join("\n");
31956
- }
31957
- function settingsBlock(w) {
31958
- return [
31959
- " ctx.registerSettingsSection({",
31960
- ` id: '${w.id}',`,
31961
- ` title: '${w.id}.settings.title',`,
31962
- " rows: [",
31963
- " {",
31964
- " id: 'enabled',",
31965
- ` label: '${w.id}.settings.enabled',`,
31966
- ` control: { kind: 'toggle', value: () => ${w.propertyName}Enabled(), set: (v) => ${w.propertyName}Enabled.set(v) },`,
31967
- " },",
31968
- " {",
31969
- " id: 'note',",
31970
- ` label: '${w.id}.settings.note',`,
31971
- ` control: { kind: 'text', value: () => ${w.propertyName}Note(), set: (v) => ${w.propertyName}Note.set(v) },`,
31972
- " },",
31973
- " ],",
31974
- " });"
31975
- ].join("\n");
31976
- }
31977
- function pluginImports(w) {
31978
- const imports = ["import { Plugin } from '@loomweaver/plugin-sdk';"];
31979
- if (w.features.container) {
31980
- imports.push(
31981
- `import { ${w.className}CanvasView } from '../views/${w.id}-canvas-view';`,
31982
- `import { ${w.className}DetailsView } from '../views/${w.id}-details-view';`
31983
- );
31984
- } else {
31985
- imports.push(`import { ${w.className}View } from '../views/${w.id}-view';`);
31986
- }
31987
- if (w.features.about) {
31988
- imports.push(
31989
- `import { ${w.className}AboutDialog } from '../dialogs/${w.id}-about-dialog';`
31990
- );
31991
- }
31992
- if (w.features.agent) {
31993
- imports.push(
31994
- `import { ${w.propertyName}Agent, ${w.propertyName}Connection } from '../agent/${w.id}-agent';`,
31995
- `import { ${w.className}AgentPanel } from '../agent/${w.id}-agent-panel';`
31996
- );
31997
- }
31998
- if (w.features.settings) {
31999
- imports.unshift("import { signal } from '@angular/core';");
32000
- }
32001
- return imports;
32002
- }
32003
- function pluginConsts(w) {
32004
- const consts = [`const icon =
32005
- '${SURFACE_ICON}';`];
32006
- if (w.features.settings) {
32007
- consts.push(
32008
- `const ${w.propertyName}Enabled = signal(true);`,
32009
- `const ${w.propertyName}Note = signal('');`
32010
- );
32011
- }
32012
- if (w.features.command) consts.push(toneHelper());
32013
- return consts;
32014
- }
32015
- function pluginBody(w) {
32016
- const body = [` ctx.contributeIcons({ '${w.id}': icon });`];
32017
- if (w.features.agent) {
32018
- body.push(
32019
- ` ${w.propertyName}Agent.set(${w.propertyName}Connection(ctx));`
32020
- );
32021
- }
32022
- if (w.features.command) body.push(commandBlock(w));
32023
- if (w.features.about) body.push(aboutCommandBlock(w));
32024
- body.push(surfaceBlock(w), railBlock(w));
32025
- if (w.features.about) body.push(aboutRailBlock(w));
32026
- if (w.features.barItem) body.push(barItemBlock(w));
32027
- if (w.features.menuSlot) body.push(menuBlock(w));
32028
- if (w.features.settings) body.push(settingsBlock(w));
32029
- if (w.features.agent) body.push(agentSurfaceBlock(w));
32030
- return body;
32031
- }
32032
- function pluginFile(w) {
32033
- const body = pluginBody(w);
32034
- const deactivate = w.features.agent ? `
32035
- deactivate() {
32036
- ${w.propertyName}Agent.set(null);
32037
- },` : "";
32038
- return `${pluginImports(w).join("\n")}
32039
-
32040
- ${pluginConsts(w).join("\n\n")}
32041
-
32042
- export const ${w.propertyName}Plugin: Plugin = {
32043
- manifest: {
32044
- id: '${w.id}',
32045
- name: '${w.name}',
32046
- capabilities: [${capabilityItems(w.capabilities)}],
32047
- },
32048
- activate(ctx) {
32049
- ${body.join("\n")}
32050
- },${deactivate}
32051
- };
32052
- `;
32053
- }
32054
-
32055
- // ../devkit/src/recipes/angular-weaver/recipe.ts
32056
- var DEFAULT_MENU_SLOT = "content/tab/context";
32057
- function accessLiteral(spec) {
32058
- if (spec === "authenticated") return "{ authenticated: true }";
32059
- if (spec === "anonymous") return "{ authenticated: false }";
32060
- if (spec.startsWith("role:")) {
32061
- const role = spec.slice("role:".length);
32062
- if (!role)
32063
- throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
32064
- if (/['\\]/.test(role))
32065
- throw new Error(
32066
- `Access role "${role}" must not contain quotes or backslashes.`
32067
- );
32068
- return `{ anyRole: ['${role}'] }`;
32069
- }
32070
- throw new Error(
32071
- `Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
32072
- );
32073
- }
32074
- function resolveMenuSlot(menu) {
32075
- if (menu === true) {
32076
- return DEFAULT_MENU_SLOT;
32077
- }
32078
- return typeof menu === "string" && menu.length ? menu : void 0;
32079
- }
32080
- var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
32081
- "cmd",
32082
- "command",
32083
- "ctrl",
32084
- "control",
32085
- "meta"
32086
- ]);
32087
- function assertPlatformNeutralChord(shortcut) {
32088
- const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
32089
- const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
32090
- if (bound) {
32091
- throw new Error(
32092
- `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.`
32093
- );
32094
- }
32095
- }
32096
- function resolveFeatures(id, input) {
32097
- const menuSlot = resolveMenuSlot(input?.menu);
32098
- const barItem = Boolean(input?.barItem);
32099
- const hasShortcut = Boolean(input?.shortcut?.trim());
32100
- if (hasShortcut) {
32101
- assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
32102
- }
32103
- const instanceable = Boolean(input?.instanceable);
32104
- const container = Boolean(input?.container);
32105
- if (instanceable && container) {
32106
- throw new Error(
32107
- '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.'
32108
- );
32109
- }
32110
- const agent = Boolean(input?.agent);
32111
- return {
32112
- command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
32113
- menuSlot,
32114
- settings: Boolean(input?.settings),
32115
- access: input?.access ? accessLiteral(input.access) : void 0,
32116
- shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
32117
- barItem,
32118
- about: Boolean(input?.about),
32119
- instanceable,
32120
- container,
32121
- agent,
32122
- spec: input?.spec !== false
32123
- };
32124
- }
32125
- function deriveCapabilities(features) {
32126
- const set2 = /* @__PURE__ */ new Set(["contributions", "navigation"]);
32127
- if (features.command) set2.add("ui");
32128
- if (features.about) {
32129
- set2.add("ui");
32130
- set2.add("host");
32131
- }
32132
- if (features.agent) {
32133
- set2.add("ui");
32134
- set2.add("automation");
32135
- }
32136
- return KNOWN_CAPABILITIES.filter((capability) => set2.has(capability));
32137
- }
32138
- function resolveWeaverInput(input) {
32139
- if (!isKebabId(input.id)) {
32140
- throw new Error(
32141
- `Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
32142
- );
32143
- }
32144
- const features = resolveFeatures(input.id, input.features);
32145
- const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
32146
- return {
32147
- id: input.id,
32148
- name: input.name?.trim() || toTitleCase(input.id),
32149
- className: toPascalCase(input.id),
32150
- propertyName: toCamelCase(input.id),
32151
- capabilities,
32152
- features,
32153
- prefix: input.prefix?.trim() || "lw",
32154
- importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
32155
- };
32156
- }
32157
- function aboutDialogFile(w) {
32233
+ // ../devkit/src/recipes/angular-weaver/weaver-views.ts
32234
+ function aboutDialogFile(weaver) {
32158
32235
  return `import { Component, inject } from '@angular/core';
32159
32236
  import { DialogRef, PluginHost } from '@loomweaver/plugin-sdk';
32160
32237
 
32161
32238
  @Component({
32162
- selector: '${w.prefix}-${w.id}-about-dialog',
32163
- templateUrl: './${w.id}-about-dialog.html',
32239
+ selector: '${weaver.prefix}-${weaver.id}-about-dialog',
32240
+ templateUrl: './${weaver.id}-about-dialog.html',
32164
32241
  })
32165
- export class ${w.className}AboutDialog {
32242
+ export class ${weaver.className}AboutDialog {
32166
32243
  protected readonly host = inject(DialogRef).data as PluginHost;
32167
32244
  }
32168
32245
  `;
32169
32246
  }
32170
- function aboutDialogTemplateFile(w) {
32247
+ function aboutDialogTemplateFile(weaver) {
32171
32248
  return `<div class="flex flex-col items-center gap-2 text-center">
32172
- <h2 class="text-lg font-semibold text-content">${w.name}</h2>
32249
+ <h2 class="text-lg font-semibold text-content">${weaver.name}</h2>
32173
32250
  <span class="text-xs text-content-faint tabular-nums">v{{ host.version() }}</span>
32174
32251
  </div>
32175
32252
  `;
32176
32253
  }
32177
- function indexFile(w) {
32178
- return `export { ${w.propertyName}Plugin } from './lib/plugin/${w.id}.plugin';
32179
- `;
32180
- }
32181
- function viewFile(w) {
32182
- if (!w.features.instanceable) {
32254
+ function viewFile(weaver) {
32255
+ if (!weaver.features.instanceable) {
32183
32256
  return `import { Component } from '@angular/core';
32184
32257
 
32185
32258
  @Component({
32186
- selector: '${w.prefix}-${w.id}-view',
32187
- templateUrl: './${w.id}-view.html',
32259
+ selector: '${weaver.prefix}-${weaver.id}-view',
32260
+ templateUrl: './${weaver.id}-view.html',
32188
32261
  })
32189
- export class ${w.className}View {}
32262
+ export class ${weaver.className}View {}
32190
32263
  `;
32191
32264
  }
32192
32265
  return `import { Component, computed, inject } from '@angular/core';
32193
32266
  import { VIEW_STATE, type ViewState } from '@loomweaver/plugin-sdk';
32194
32267
 
32195
- interface ${w.className}State {
32268
+ interface ${weaver.className}State {
32196
32269
  readonly sort: 'natural' | 'alpha';
32197
32270
  }
32198
32271
 
32199
- const FRESH: ${w.className}State = { sort: 'natural' };
32272
+ const FRESH: ${weaver.className}State = { sort: 'natural' };
32200
32273
 
32201
32274
  @Component({
32202
- selector: '${w.prefix}-${w.id}-view',
32203
- templateUrl: './${w.id}-view.html',
32275
+ selector: '${weaver.prefix}-${weaver.id}-view',
32276
+ templateUrl: './${weaver.id}-view.html',
32204
32277
  })
32205
- export class ${w.className}View {
32206
- private readonly viewState = inject(VIEW_STATE) as ViewState<${w.className}State>;
32278
+ export class ${weaver.className}View {
32279
+ private readonly viewState = inject(VIEW_STATE) as ViewState<${weaver.className}State>;
32207
32280
 
32208
32281
  // undefined = a fresh instance, so apply your own default.
32209
32282
  private readonly state = computed(() => this.viewState.value() ?? FRESH);
@@ -32220,8 +32293,8 @@ export class ${w.className}View {
32220
32293
  }
32221
32294
  `;
32222
32295
  }
32223
- function viewTemplateFile(w) {
32224
- const stateNote = w.features.instanceable ? ` <button type="button" class="lw-btn lw-btn--default self-start" (click)="toggleSort()">
32296
+ function viewTemplateFile(weaver) {
32297
+ const stateNote = weaver.features.instanceable ? ` <button type="button" class="lw-btn lw-btn--default self-start" (click)="toggleSort()">
32225
32298
  Sort: {{ sort() }}
32226
32299
  </button>
32227
32300
  <p class="text-sm text-content-faint">
@@ -32230,7 +32303,7 @@ function viewTemplateFile(w) {
32230
32303
  </p>
32231
32304
  ` : "";
32232
32305
  return `<div class="mx-auto flex max-w-2xl flex-col gap-4 p-6">
32233
- <h2 class="text-lg font-semibold text-content">${w.name}</h2>
32306
+ <h2 class="text-lg font-semibold text-content">${weaver.name}</h2>
32234
32307
  <p class="text-sm text-content-faint">
32235
32308
  Your new weaver surface. Register more surfaces, commands, rail items and menus on
32236
32309
  <code class="text-content">ctx</code> inside the plugin's
@@ -32239,13 +32312,13 @@ function viewTemplateFile(w) {
32239
32312
  ${stateNote}</div>
32240
32313
  `;
32241
32314
  }
32242
- function childViewFile(w, suffix, className) {
32315
+ function childViewFile(weaver, suffix, className) {
32243
32316
  return `import { Component, inject } from '@angular/core';
32244
32317
  import { ActivatedRoute } from '@angular/router';
32245
32318
 
32246
32319
  @Component({
32247
- selector: '${w.prefix}-${w.id}-${suffix}-view',
32248
- templateUrl: './${w.id}-${suffix}-view.html',
32320
+ selector: '${weaver.prefix}-${weaver.id}-${suffix}-view',
32321
+ templateUrl: './${weaver.id}-${suffix}-view.html',
32249
32322
  })
32250
32323
  export class ${className} {
32251
32324
  private readonly route = inject(ActivatedRoute, { optional: true });
@@ -32255,7 +32328,7 @@ export class ${className} {
32255
32328
  }
32256
32329
  `;
32257
32330
  }
32258
- function childViewTemplateFile(w, heading) {
32331
+ function childViewTemplateFile(weaver, heading) {
32259
32332
  return `<div class="flex h-full flex-col gap-3 p-4">
32260
32333
  <h3 class="text-sm font-semibold text-content">${heading}</h3>
32261
32334
  <p class="text-sm text-content-faint">
@@ -32266,60 +32339,66 @@ function childViewTemplateFile(w, heading) {
32266
32339
  </div>
32267
32340
  `;
32268
32341
  }
32269
- function specFile2(w) {
32270
- return `import { ${w.propertyName}Plugin } from './${w.id}.plugin';
32342
+ function specFile2(weaver) {
32343
+ return `import { ${weaver.propertyName}Plugin } from './${weaver.id}.plugin';
32271
32344
 
32272
- describe('${w.propertyName}Plugin', () => {
32345
+ describe('${weaver.propertyName}Plugin', () => {
32273
32346
  it('declares its manifest', () => {
32274
- expect(${w.propertyName}Plugin.manifest.id).toBe('${w.id}');
32275
- expect(${w.propertyName}Plugin.manifest.capabilities).toContain('contributions');
32347
+ expect(${weaver.propertyName}Plugin.manifest.id).toBe('${weaver.id}');
32348
+ expect(${weaver.propertyName}Plugin.manifest.capabilities).toContain('contributions');
32276
32349
  });
32277
32350
  });
32278
32351
  `;
32279
32352
  }
32353
+
32354
+ // ../devkit/src/recipes/angular-weaver/recipe.ts
32355
+ function indexFile(weaver) {
32356
+ return `export { ${weaver.propertyName}Plugin } from './lib/plugin/${weaver.id}.plugin';
32357
+ `;
32358
+ }
32280
32359
  var angularWeaver = {
32281
32360
  id: "angular-weaver",
32282
32361
  build(input) {
32283
- const w = resolveWeaverInput(input);
32362
+ const weaver = resolveWeaverInput(input);
32284
32363
  const files = {
32285
- "src/index.ts": indexFile(w),
32286
- [`src/lib/plugin/${w.id}.plugin.ts`]: pluginFile(w),
32287
- "src/lib/i18n/en.json": i18nFile(w),
32288
- "src/lib/i18n/de.json": i18nFile(w),
32289
- "README.md": readmeFile(w)
32364
+ "src/index.ts": indexFile(weaver),
32365
+ [`src/lib/plugin/${weaver.id}.plugin.ts`]: pluginFile(weaver),
32366
+ "src/lib/i18n/en.json": i18nFile(weaver),
32367
+ "src/lib/i18n/de.json": i18nFile(weaver),
32368
+ "README.md": readmeFile(weaver)
32290
32369
  };
32291
- if (w.features.container) {
32292
- files[`src/lib/views/${w.id}-canvas-view.ts`] = childViewFile(
32293
- w,
32370
+ if (weaver.features.container) {
32371
+ files[`src/lib/views/${weaver.id}-canvas-view.ts`] = childViewFile(
32372
+ weaver,
32294
32373
  "canvas",
32295
- `${w.className}CanvasView`
32374
+ `${weaver.className}CanvasView`
32296
32375
  );
32297
- files[`src/lib/views/${w.id}-canvas-view.html`] = childViewTemplateFile(
32298
- w,
32376
+ files[`src/lib/views/${weaver.id}-canvas-view.html`] = childViewTemplateFile(
32377
+ weaver,
32299
32378
  "Canvas"
32300
32379
  );
32301
- files[`src/lib/views/${w.id}-details-view.ts`] = childViewFile(
32302
- w,
32380
+ files[`src/lib/views/${weaver.id}-details-view.ts`] = childViewFile(
32381
+ weaver,
32303
32382
  "details",
32304
- `${w.className}DetailsView`
32383
+ `${weaver.className}DetailsView`
32305
32384
  );
32306
- files[`src/lib/views/${w.id}-details-view.html`] = childViewTemplateFile(
32307
- w,
32385
+ files[`src/lib/views/${weaver.id}-details-view.html`] = childViewTemplateFile(
32386
+ weaver,
32308
32387
  "Details"
32309
32388
  );
32310
32389
  } else {
32311
- files[`src/lib/views/${w.id}-view.ts`] = viewFile(w);
32312
- files[`src/lib/views/${w.id}-view.html`] = viewTemplateFile(w);
32390
+ files[`src/lib/views/${weaver.id}-view.ts`] = viewFile(weaver);
32391
+ files[`src/lib/views/${weaver.id}-view.html`] = viewTemplateFile(weaver);
32313
32392
  }
32314
- if (w.features.about) {
32315
- files[`src/lib/dialogs/${w.id}-about-dialog.ts`] = aboutDialogFile(w);
32316
- files[`src/lib/dialogs/${w.id}-about-dialog.html`] = aboutDialogTemplateFile(w);
32393
+ if (weaver.features.about) {
32394
+ files[`src/lib/dialogs/${weaver.id}-about-dialog.ts`] = aboutDialogFile(weaver);
32395
+ files[`src/lib/dialogs/${weaver.id}-about-dialog.html`] = aboutDialogTemplateFile(weaver);
32317
32396
  }
32318
- if (w.features.agent) {
32319
- Object.assign(files, agentFiles(w));
32397
+ if (weaver.features.agent) {
32398
+ Object.assign(files, agentFiles(weaver));
32320
32399
  }
32321
- if (w.features.spec) {
32322
- files[`src/lib/plugin/${w.id}.plugin.spec.ts`] = specFile2(w);
32400
+ if (weaver.features.spec) {
32401
+ files[`src/lib/plugin/${weaver.id}.plugin.spec.ts`] = specFile2(weaver);
32323
32402
  }
32324
32403
  return files;
32325
32404
  }
@@ -32328,16 +32407,16 @@ var angularWeaver = {
32328
32407
  // ../devkit/src/recipes/frame-plugin/recipe.ts
32329
32408
  function resolveFramePluginInput(input) {
32330
32409
  if (!isKebabId(input.id)) {
32331
- throw new Error(`Sandbox plugin id must be kebab-case (e.g. "notes"); got "${input.id}".`);
32410
+ throw new Error(`Frame plugin id must be kebab-case (e.g. "notes"); got "${input.id}".`);
32332
32411
  }
32333
32412
  return { id: input.id, name: input.name?.trim() || toTitleCase(input.id) };
32334
32413
  }
32335
- function pluginHtml(p) {
32414
+ function pluginHtml(plugin) {
32336
32415
  return `<!doctype html>
32337
32416
  <html lang="en">
32338
32417
  <head>
32339
32418
  <meta charset="utf-8" />
32340
- <title>${p.name} \u2014 frame plugin (logic)</title>
32419
+ <title>${plugin.name} \u2014 frame plugin (logic)</title>
32341
32420
  </head>
32342
32421
  <body>
32343
32422
  <script src="/frame-kit/penpal.global.js"></script>
@@ -32346,7 +32425,7 @@ function pluginHtml(p) {
32346
32425
  </html>
32347
32426
  `;
32348
32427
  }
32349
- function pluginJs(p) {
32428
+ function pluginJs(plugin) {
32350
32429
  return `(function () {
32351
32430
  const Penpal = globalThis.Penpal;
32352
32431
  const messenger = new Penpal.WindowMessenger({
@@ -32358,27 +32437,27 @@ function pluginJs(p) {
32358
32437
  connection.promise
32359
32438
  .then(function (ctx) {
32360
32439
  return Promise.all([
32361
- ctx.toast({ message: '${p.name} ready', kind: 'success', timeoutMs: 4000 }),
32440
+ ctx.toast({ message: '${plugin.name} ready', kind: 'success', timeoutMs: 4000 }),
32362
32441
  ctx.registerSurface({
32363
- id: '${p.id}.view',
32364
- title: '${p.name}',
32365
- iframe: '/${p.id}/view.html',
32366
- routable: { path: '${p.id}', titleIsLiteral: true },
32442
+ id: '${plugin.id}.view',
32443
+ title: '${plugin.name}',
32444
+ iframe: '/${plugin.id}/view.html',
32445
+ routable: { path: '${plugin.id}', titleIsLiteral: true },
32367
32446
  }),
32368
32447
  ]);
32369
32448
  })
32370
32449
  .catch(function (error) {
32371
- console.error('[${p.id}] frame plugin failed', error);
32450
+ console.error('[${plugin.id}] frame plugin failed', error);
32372
32451
  });
32373
32452
  })();
32374
32453
  `;
32375
32454
  }
32376
- function viewHtml(p) {
32455
+ function viewHtml(plugin) {
32377
32456
  return `<!doctype html>
32378
32457
  <html lang="en">
32379
32458
  <head>
32380
32459
  <meta charset="utf-8" />
32381
- <title>${p.name}</title>
32460
+ <title>${plugin.name}</title>
32382
32461
  <link rel="stylesheet" href="/frame-kit/lw-frame.css" />
32383
32462
  <style>
32384
32463
  body {
@@ -32394,7 +32473,7 @@ function viewHtml(p) {
32394
32473
  </head>
32395
32474
  <body>
32396
32475
  <div class="wrap">
32397
- <h1>${p.name}</h1>
32476
+ <h1>${plugin.name}</h1>
32398
32477
  <p>
32399
32478
  Your sandboxed surface. It runs isolated in its own iframe, so its body can be built with
32400
32479
  any framework (React, Vue, Svelte, vanilla). The frame UI kit (served by the distribution
@@ -32422,16 +32501,16 @@ function viewHtml(p) {
32422
32501
  </html>
32423
32502
  `;
32424
32503
  }
32425
- function readme(p) {
32504
+ function readme(plugin) {
32426
32505
  return [
32427
- `# ${p.name} \u2014 frame plugin`,
32506
+ `# ${plugin.name} \u2014 frame plugin`,
32428
32507
  "",
32429
32508
  `A framework-agnostic LoomWeaver plugin: it runs in an isolated \`<iframe sandbox>\` and`,
32430
32509
  `receives \`ctx\` over Penpal RPC through the same default-deny broker a trusted plugin uses.`,
32431
32510
  "",
32432
32511
  "## Serve it + wire it into a distribution",
32433
32512
  "",
32434
- `1. Put these files under the distribution's static dir, e.g. \`public/${p.id}/\`. The plugin`,
32513
+ `1. Put these files under the distribution's static dir, e.g. \`public/${plugin.id}/\`. The plugin`,
32435
32514
  ` references the **frame UI kit** (\`@loomweaver/frame-kit\`) at \`/frame-kit/\` \u2014`,
32436
32515
  ` the distribution serves it via an assets glob (generated distributions already do):`,
32437
32516
  "",
@@ -32442,43 +32521,40 @@ function readme(p) {
32442
32521
  `2. Register + grant it in the composition root:`,
32443
32522
  "",
32444
32523
  " ```ts",
32445
- ` provideFramePlugins({ id: '${p.id}', entryUrl: '/${p.id}/plugin.html', capabilities: ['contributions', 'ui'] });`,
32446
- ` provideCapabilityGrants({ '${p.id}': ['contributions', 'ui'] });`,
32524
+ ` provideFramePlugins({ id: '${plugin.id}', entryUrl: '/${plugin.id}/plugin.html', capabilities: ['contributions', 'ui'] });`,
32525
+ ` provideCapabilityGrants({ '${plugin.id}': ['contributions', 'ui'] });`,
32447
32526
  " ```",
32448
32527
  "",
32449
- `The surface is routable at \`/${p.id}\`. Replace \`view.html\` with your own UI in any framework.`,
32528
+ `The surface is routable at \`/${plugin.id}\`. Replace \`view.html\` with your own UI in any framework.`,
32450
32529
  ""
32451
32530
  ].join("\n");
32452
32531
  }
32453
32532
  var framePlugin = {
32454
32533
  id: "frame-plugin",
32455
32534
  build(input) {
32456
- const p = resolveFramePluginInput(input);
32535
+ const plugin = resolveFramePluginInput(input);
32457
32536
  return {
32458
- "plugin.html": pluginHtml(p),
32459
- "plugin.js": pluginJs(p),
32460
- "view.html": viewHtml(p),
32461
- "README.md": readme(p)
32537
+ "plugin.html": pluginHtml(plugin),
32538
+ "plugin.js": pluginJs(plugin),
32539
+ "view.html": viewHtml(plugin),
32540
+ "README.md": readme(plugin)
32462
32541
  };
32463
32542
  }
32464
32543
  };
32465
32544
 
32466
- // ../devkit/src/recipes/platform-version.ts
32467
- var PLATFORM_VERSION = "0.14.1";
32468
-
32469
32545
  // ../devkit/src/recipes/angular-distribution/amendments.ts
32470
32546
  var DISTRIBUTION_INITIAL_BUDGET = {
32471
32547
  warning: "1.5MB",
32472
32548
  error: "2MB"
32473
32549
  };
32474
- function distributionAmendments(d) {
32550
+ function distributionAmendments(distribution) {
32475
32551
  return [
32476
32552
  {
32477
32553
  kind: "package",
32478
32554
  name: "@loomweaver/frame-kit",
32479
32555
  version: `^${PLATFORM_VERSION}`
32480
32556
  },
32481
- ...d.styles === "tailwind" ? [
32557
+ ...distribution.styles === "tailwind" ? [
32482
32558
  {
32483
32559
  kind: "postcss",
32484
32560
  file: ".postcssrc.json",
@@ -32510,26 +32586,13 @@ function distributionAmendments(d) {
32510
32586
  ];
32511
32587
  }
32512
32588
 
32513
- // ../devkit/src/recipes/shell-regions.ts
32514
- var SHELL_REGIONS = [
32515
- "{ id: 'top-bar', type: 'bar', dock: 'top' }",
32516
- "{ id: 'primary', type: 'rail', dock: 'left' }",
32517
- "{ id: 'left-panel', type: 'panel', dock: 'left' }",
32518
- "{ id: 'right-panel', type: 'panel', dock: 'right' }",
32519
- "{ id: 'main', type: 'content', dock: 'center' }",
32520
- "{ id: 'status-bar', type: 'bar', dock: 'bottom' }"
32521
- ];
32522
- function renderRegions(indent) {
32523
- return SHELL_REGIONS.map((region) => `${indent}${region},`).join("\n");
32524
- }
32525
-
32526
32589
  // ../devkit/src/recipes/angular-distribution/logo.ts
32527
32590
  var PLACEHOLDER_LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="512" height="512" viewBox="87.5 -53 1129.9 1129.9"><g paint-order="stroke"><path d="m0 0 12.6-10.6-9.2-8-14.5 12.1q-.7.8 1.2 1.5A17 17 0 0 1-2.4-.5Q-1 .7 0 0" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 265.2 368.8)"/><path d="m0 0 19-16c.6-.5.5-1.6-.2-2.3q-2.8-3-7.5-4.5c-1.2-.4-2.8-.1-3.4.4l-17 14.3z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 593.2 644.5)"/><path d="m0 0 5.8-4.9-9.2-8-2.8 2.3q-.9 1 0 2.5l4 7.4Q-1 .7 0 0" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#c59a2f;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 386.3 170)"/><path d="m0 0 12.4-10.4-9.2-8.1L-9.2-8.1Z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#c59a2f;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 608.2 356.6)"/><path d="m0 0 5-4.2c.6-.5.6-1.6 0-2.5l-4-7.4q-1-1.4-2.1-.7l-8 6.7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#c59a2f;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 933.4 630)"/><path d="m0 0 14.6-12.3-9.2-8L-11-6.6c-.6.5-.6 1.6.2 2.4Q-8-1.2-3.3.3C-2.2.7-.6.5 0 0" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 589.5 28.9)"/><path d="m0 0 17-14.3q.8-.8-1.3-1.4a17 17 0 0 1-7.5-4.5c-.7-.8-1.8-1-2.4-.5l-15 12.6Z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 948.5 330.8)"/><path d="m0 0 8.4-7-13.6-12-8.4 7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 514.5 213.2)"/><path d="m0 0 5.7-4.8q1.3-1 .4-2L3-9.5l-8.4 7 3 2.7A2 2 0 0 0 0 0" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 743 48)"/><path d="m0 0 8.4-7-2.2-2q-1-.7-2.3.2L-2-4q-1 1.2-.2 2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 158.6 526.5)"/><path d="m0 0 8.4-7L-5-18.9c-.5-.5-1.6-.4-2.3.2l-5.8 4.8q-1 1-.3 2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 492.8 507)"/><path d="m0 0 5.7-4.8q1.3-1 .4-2l-14.7-13-8.4 7.1L-2.3.3C-1.8.6-.7.5 0 0" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 902.6 182)"/><path d="m0 0 5.9-5Q7-6 6.2-7l-3-2.7-8.7 7.2L-2.4.3Q-1.4.9 0 0" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 1065.8 319.3)"/><path d="m0 0 8.6-7.2-13.6-12-8.6 7.2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 836.9 484.2)"/><path d="m0 0 8.6-7.2-2.2-2q-1-.6-2.4.3l-5.9 5Q-3-3-2.2-2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#2e96c9;fill-rule:nonzero;opacity:1" transform="matrix(15.6303 0 0 -15.6303 481 797.5)"/><g transform="matrix(15.6303 0 0 -15.6303 877.6 706.6)"><linearGradient id="a" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 64.4 103.4)scale(2.18666 -2.18666)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#c59a2f;stop-opacity:1"/><stop offset="100%" style="stop-color:#614d06;stop-opacity:1"/></linearGradient><path d="m176.7 129.6 2-1.7 9.2 8-2 1.8z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#a);fill-rule:nonzero;opacity:1" transform="translate(-182.3 -132.8)"/></g><g transform="matrix(15.6303 0 0 -15.6303 711.4 567.2)"><linearGradient id="b" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 56.7 106)scale(-2.18726 2.18726)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#c59a2f;stop-opacity:1"/><stop offset="100%" style="stop-color:#614d06;stop-opacity:1"/></linearGradient><path d="m166 138.5 2.1-1.7 9.2 8.1-2 1.7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#b);fill-rule:nonzero;opacity:1" transform="translate(-171.7 -141.7)"/></g><g transform="matrix(15.6303 0 0 -15.6303 532.5 867.7)"><linearGradient id="c" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(45.7 -65.1 251)scale(2.11643 -2.11643)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m155 125.2 8.5-7.2 2 1.8-8.6 7.2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#c);fill-rule:nonzero;opacity:1" transform="translate(-160.2 -122.5)"/></g><g transform="matrix(15.6303 0 0 -15.6303 707.2 713.6)"><linearGradient id="d" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(-134.3 114 30.2)scale(2.11524 -2.11524)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m166.1 135 8.6-7.1 2 1.7-8.6 7.2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#d);fill-rule:nonzero;opacity:1" transform="translate(-171.4 -132.3)"/></g><g transform="matrix(15.6303 0 0 -15.6303 888.2 554.5)"><linearGradient id="e" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(45.7 -77.5 288)scale(2.11554 -2.11554)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m177.7 145.3 8.6-7.3 2 1.8-8.6 7.2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#e);fill-rule:nonzero;opacity:1" transform="translate(-183 -142.5)"/></g><g transform="matrix(15.6303 0 0 -15.6303 1062 401.2)"><linearGradient id="f" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(45.7 -84.1 307.3)scale(-2.11583 2.11583)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m188.8 155 8.6-7.2 2 1.8-8.6 7.2z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#f);fill-rule:nonzero;opacity:1" transform="translate(-194.1 -152.3)"/></g><g transform="matrix(15.6303 0 0 -15.6303 206.5 595.5)"><linearGradient id="g" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(225.7 98.9 40.5)scale(-2.0962 2.0962)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m134.3 142.5 8.2-7 2 1.8-8.3 7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#g);fill-rule:nonzero;opacity:1" transform="translate(-139.4 -139.9)"/></g><g transform="matrix(15.6303 0 0 -15.6303 381 441.4)"><linearGradient id="h" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(-134.3 107 43.3)scale(2.0962 -2.0962)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m145.4 152.3 8.3-6.9 2 1.8-8.3 6.9z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#h);fill-rule:nonzero;opacity:1" transform="translate(-150.5 -149.8)"/></g><g transform="matrix(15.6303 0 0 -15.6303 562 282.3)"><linearGradient id="i" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(225.7 114.5 45.7)scale(-2.0953 2.0953)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m157 162.5 8.2-7 2 1.8-8.2 7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#i);fill-rule:nonzero;opacity:1" transform="translate(-162.1 -160)"/></g><g transform="matrix(15.6303 0 0 -15.6303 736 129)"><linearGradient id="j" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(45.7 -115.1 291)scale(-2.0956 2.0956)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m168.1 172.3 8.3-6.9 2 1.7-8.3 7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#j);fill-rule:nonzero;opacity:1" transform="translate(-173.2 -169.7)"/></g><g transform="matrix(15.6303 0 0 -15.6303 544.8 441.3)"><linearGradient id="k" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 50.4 108)scale(2.18666 -2.18666)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#c59a2f;stop-opacity:1"/><stop offset="100%" style="stop-color:#614d06;stop-opacity:1"/></linearGradient><path d="m155.9 147 2-1.6 9.2 8-2 1.8z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#k);fill-rule:nonzero;opacity:1" transform="translate(-161.5 -150.3)"/></g><g transform="matrix(15.6303 0 0 -15.6303 390.7 294.8)"><linearGradient id="l" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 42.9 110.5)scale(-2.17357 2.17357)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#c59a2f;stop-opacity:1"/><stop offset="100%" style="stop-color:#614d06;stop-opacity:1"/></linearGradient><path d="m145.6 156 2-1.7 9 8-2 1.6z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#l);fill-rule:nonzero;opacity:1" transform="translate(-151.2 -159.1)"/></g><g transform="matrix(15.6303 0 0 -15.6303 375.9 585.7)"><linearGradient id="m" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 46.2 101)scale(-1.9968 1.9968)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m144.7 137.2 1.8-1.5 9.2 8.1-1.8 1.5z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#m);fill-rule:nonzero;opacity:1" transform="translate(-150.2 -140.5)"/></g><g transform="matrix(15.6303 0 0 -15.6303 733.7 274)"><linearGradient id="n" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(-44.3 284 -131.7)scale(1.9968 -1.9968)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m167.6 157.2 1.8-1.5 9.2 8-1.8 1.6z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#n);fill-rule:nonzero;opacity:1" transform="translate(-173.1 -160.5)"/></g><g transform="matrix(15.6303 0 0 -15.6303 893.6 408.2)"><linearGradient id="o" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 61 113.2)scale(1.9962 -1.9962)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m177.8 148.6 1.8-1.5 9.2 8-1.8 1.6z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#o);fill-rule:nonzero;opacity:1" transform="translate(-183.3 -151.9)"/></g><g transform="matrix(15.6303 0 0 -15.6303 535.9 720)"><linearGradient id="p" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(135.7 53.6 98.6)scale(1.9962 -1.9962)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m155 128.6 1.7-1.5 9.2 8.1-1.8 1.5z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#p);fill-rule:nonzero;opacity:1" transform="translate(-160.4 -132)"/></g><g transform="matrix(15.6303 0 0 -15.6303 717.3 421.6)"><linearGradient id="q" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(45.7 -93.5 280)scale(-2.095 2.095)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m167 153.6 8.2-7 2 1.8-8.3 7z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#q);fill-rule:nonzero;opacity:1" transform="translate(-172 -151)"/></g><g transform="matrix(15.6303 0 0 -15.6303 542.1 575.9)"><linearGradient id="r" x1="0" x2="1" y1="0" y2="0" gradientTransform="rotate(45.7 -87 261)scale(2.0956 -2.0956)" gradientUnits="userSpaceOnUse"><stop offset="0%" style="stop-color:#2e96c9;stop-opacity:1"/><stop offset="100%" style="stop-color:#2479a3;stop-opacity:1"/></linearGradient><path d="m155.7 143.8 8.3-7 2 1.8-8.3 6.9z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:url(#r);fill-rule:nonzero;opacity:1" transform="translate(-160.8 -141.2)"/></g></g></svg>
32528
32591
  `;
32529
32592
 
32530
32593
  // ../devkit/src/recipes/angular-distribution/readme.ts
32531
- function stylesNotes(d) {
32532
- if (d.styles === "precompiled") {
32594
+ function stylesNotes(distribution) {
32595
+ if (distribution.styles === "precompiled") {
32533
32596
  return [
32534
32597
  "`src/styles.css` imports the stylesheet **we** compiled \u2014 tokens, the `.lw-*` class contracts",
32535
32598
  "and every utility the shell's own templates use, 67 KB minified and 11 KB over the wire. There",
@@ -32567,9 +32630,9 @@ function stylesNotes(d) {
32567
32630
  "Bootstrap, Bulma or hand-written CSS."
32568
32631
  ];
32569
32632
  }
32570
- function readme2(d) {
32633
+ function readme2(distribution) {
32571
32634
  return [
32572
- `# ${d.title} \u2014 a LoomWeaver distribution`,
32635
+ `# ${distribution.title} \u2014 a LoomWeaver distribution`,
32573
32636
  "",
32574
32637
  `The composition root that assembles the platform into a shippable product. It renders the bare`,
32575
32638
  `shell out of the box; add your weavers and branding below. Your own README is untouched \u2014 the`,
@@ -32577,29 +32640,23 @@ function readme2(d) {
32577
32640
  "",
32578
32641
  "## Run it",
32579
32642
  "",
32580
- "In an Nx workspace (the generator wired the project):",
32581
- "",
32582
32643
  "```sh",
32583
- `nx serve ${d.name}`,
32644
+ `nx serve ${distribution.name} # in an Nx workspace`,
32645
+ "ng serve # in an Angular CLI application",
32584
32646
  "```",
32585
32647
  "",
32586
- "Scaffolded over the CLI or MCP, these files are sources without build wiring \u2014 drop them into",
32587
- "an application you already serve (`ng new`, or an Nx application). They keep Angular's own",
32588
- "shape: `main.ts` bootstraps `App`, `App` renders `<lw-shell />`, and everything this product is",
32589
- "made of lives in `app.config.ts`. Nothing of the generated app is deleted. Over an existing",
32590
- "application `--force` replaces exactly the files above \u2014 all of them bootstrap wiring, none of",
32591
- "them content you authored. Under Nx it additionally merges the build targets into that",
32592
- "project's `project.json`, keeping the targets, `implicitDependencies` and tags it already had.",
32648
+ "The files keep Angular's own shape: `main.ts` bootstraps `App`, `App` renders `<lw-shell />`, and",
32649
+ "everything this product is made of lives in `app.config.ts`. Nothing of the generated app is",
32650
+ "deleted. Over an existing application `--force` replaces exactly the files above, all of them",
32651
+ "bootstrap wiring and none of them content you authored. Whether the build around them was wired",
32652
+ 'as well depends on how they arrived; see "Build wiring" below.',
32593
32653
  "",
32594
32654
  "Two leftovers from `ng new` are no longer referenced and can go: `src/app/app.routes.ts` (the",
32595
32655
  "shell owns content routing via `provideShellRouter()`) and `src/app/app.css`. The starter test",
32596
32656
  "`src/app/app.spec.ts` was replaced by one that boots the shell with this composition root, so",
32597
32657
  "`ng test` is green as generated; test your own components beside it.",
32598
32658
  "",
32599
- "The project is generated **untagged**: Nx tags belong to your `depConstraints`, and inventing",
32600
- "one would fail a lint policy you never opted this project into. If your workspace enforces",
32601
- "module boundaries, give it tags your constraints allow \u2014 `--tags` at generation time, or",
32602
- "`tags` in `project.json` afterwards.",
32659
+ ...UNTAGGED_NOTE,
32603
32660
  "",
32604
32661
  "## Compose weavers + branding (in `src/app/app.config.ts`)",
32605
32662
  "",
@@ -32647,7 +32704,7 @@ function readme2(d) {
32647
32704
  "",
32648
32705
  "```ts",
32649
32706
  "provideCommandPaletteEntry(); // top bar, end slot, order 5",
32650
- "provideQuickOpenEntry({ bar: 'top-bar', order: 4 }); // \u2026or put it wherever you want",
32707
+ `provideQuickOpenEntry({ bar: '${TOP_BAR_REGION}', order: 4 }); // \u2026or put it wherever you want`,
32651
32708
  "```",
32652
32709
  "",
32653
32710
  "A badge never outlives what it opens. Drop the search and its badge goes with it; the same",
@@ -32680,13 +32737,21 @@ function readme2(d) {
32680
32737
  "",
32681
32738
  "## Styles",
32682
32739
  "",
32683
- ...stylesNotes(d),
32740
+ ...stylesNotes(distribution),
32684
32741
  "",
32685
32742
  "## Build wiring",
32686
32743
  "",
32687
- "The scaffold did this. Your build target now names the stylesheet, three asset globs, the",
32688
- "service worker and one production setting, and the run that wrote these files listed each one it",
32689
- "added. Anything you had already set was left exactly as you set it.",
32744
+ "The build target needs the stylesheet, three asset globs, the service worker and two production",
32745
+ "settings. Who added them depends on how these files arrived:",
32746
+ "",
32747
+ "- **The Nx generator** wrote them into the project's `project.json`. Composing over an existing",
32748
+ " project with `--force`, it merged them in value by value: every value the project already had",
32749
+ " stays, and what the workbench needs is added beside it.",
32750
+ "- **The CLI inside an Angular CLI application** added what was missing to the build target in",
32751
+ " `angular.json` and listed each line it added. Anything you had already set was left as you set",
32752
+ " it.",
32753
+ "- **The MCP server**, and the CLI anywhere else, cannot reach your build configuration, so they",
32754
+ " returned these as steps still to do by hand.",
32690
32755
  "",
32691
32756
  "What each is for, so that nobody removes one as clutter. The **`@loomweaver/shell/i18n` glob**",
32692
32757
  "serves the strings the shell fetches at runtime; without it every label in the chrome renders as",
@@ -32746,7 +32811,7 @@ function readme2(d) {
32746
32811
  }
32747
32812
 
32748
32813
  // ../devkit/src/recipes/angular-distribution/recipe.ts
32749
- var STYLES = ["tailwind", "precompiled"];
32814
+ var DISTRIBUTION_STYLES = ["tailwind", "precompiled"];
32750
32815
  function resolveDistributionInput(input) {
32751
32816
  if (!isKebabId(input.name)) {
32752
32817
  throw new Error(
@@ -32754,9 +32819,9 @@ function resolveDistributionInput(input) {
32754
32819
  );
32755
32820
  }
32756
32821
  const styles = input.styles ?? "tailwind";
32757
- if (!STYLES.includes(styles)) {
32822
+ if (!DISTRIBUTION_STYLES.includes(styles)) {
32758
32823
  throw new Error(
32759
- `Unknown styles option "${styles}"; expected one of ${STYLES.join(", ")}.`
32824
+ `Unknown styles option "${styles}"; expected one of ${DISTRIBUTION_STYLES.join(", ")}.`
32760
32825
  );
32761
32826
  }
32762
32827
  const directory = input.directory === void 0 ? `apps/${input.name}` : input.directory.trim();
@@ -32777,7 +32842,7 @@ import { App } from './app/app';
32777
32842
  bootstrapApplication(App, appConfig).catch((err) => console.error(err));
32778
32843
  `;
32779
32844
  }
32780
- function appConfigTs(d) {
32845
+ function appConfigTs(distribution) {
32781
32846
  return `import { ApplicationConfig } from '@angular/core';
32782
32847
  import {
32783
32848
  provideCommandPaletteEntry,
@@ -32790,8 +32855,8 @@ import {
32790
32855
  import { provideProductIdentity } from '@loomweaver/plugin-sdk';
32791
32856
 
32792
32857
  /* Which regions exist and where they dock. Contributions target these ids, so a region a
32793
- weaver names but this layout omits renders nothing \u2014 silently. 'primary' (rail) and
32794
- 'status-bar' (bar) are what the scaffolded weaver targets. */
32858
+ weaver names but this layout omits renders nothing \u2014 silently. '${RAIL_REGION}' (rail) and
32859
+ '${STATUS_BAR_REGION}' (bar) are what the scaffolded weaver targets. */
32795
32860
  export const layout: ShellLayout = {
32796
32861
  regions: [
32797
32862
  ${renderRegions(" ".repeat(4))}
@@ -32813,7 +32878,7 @@ export const appConfig: ApplicationConfig = {
32813
32878
  provideCommandPaletteEntry(),
32814
32879
  provideQuickOpenEntry(),
32815
32880
  provideProductIdentity({
32816
- name: '${d.title}',
32881
+ name: '${distribution.title}',
32817
32882
  tagline: 'Built on LoomWeaver',
32818
32883
  logoUrl: 'logo.svg',
32819
32884
  }),
@@ -32846,7 +32911,7 @@ function appConfigSpec() {
32846
32911
  describe('layout', () => {
32847
32912
  it('declares the regions contributions target', () => {
32848
32913
  const ids = layout.regions.map((region) => region.id);
32849
- for (const id of ['primary', 'status-bar', 'main']) {
32914
+ for (const id of ['${RAIL_REGION}', '${STATUS_BAR_REGION}', '${CONTENT_REGION}']) {
32850
32915
  expect(ids).toContain(id);
32851
32916
  }
32852
32917
  });
@@ -32878,12 +32943,12 @@ describe('App', () => {
32878
32943
  });
32879
32944
  `;
32880
32945
  }
32881
- function indexHtml(d) {
32946
+ function indexHtml(distribution) {
32882
32947
  return `<!DOCTYPE html>
32883
32948
  <html lang="en">
32884
32949
  <head>
32885
32950
  <meta charset="utf-8" />
32886
- <title>${d.title}</title>
32951
+ <title>${distribution.title}</title>
32887
32952
  <base href="/" />
32888
32953
  <meta
32889
32954
  http-equiv="Content-Security-Policy"
@@ -32921,7 +32986,7 @@ function precompiledCss() {
32921
32986
  @import '@loomweaver/shell/styles/shell.css';
32922
32987
  `;
32923
32988
  }
32924
- function tailwindCss(d) {
32989
+ function tailwindCss(distribution) {
32925
32990
  return `@import 'tailwindcss';
32926
32991
 
32927
32992
  /* LoomWeaver design tokens + theme (light/dark, brand colors). Every @import has to precede the
@@ -32934,12 +32999,12 @@ function tailwindCss(d) {
32934
32999
  /* Generate the utility classes the shell (and your own components) use. The @source path must
32935
33000
  reach your workspace's node_modules FROM THIS FILE \u2014 adjust the ../ hops if this project does
32936
33001
  not sit at that depth, or Tailwind silently emits none of the shell's classes. */
32937
- @source '${d.nodeModulesFromSrc}/@loomweaver/shell';
33002
+ @source '${distribution.nodeModulesFromSrc}/@loomweaver/shell';
32938
33003
  @source './';
32939
33004
  `;
32940
33005
  }
32941
- function stylesCss(d) {
32942
- return d.styles === "precompiled" ? precompiledCss() : tailwindCss(d);
33006
+ function stylesCss(distribution) {
33007
+ return distribution.styles === "precompiled" ? precompiledCss() : tailwindCss(distribution);
32943
33008
  }
32944
33009
  function ngswConfig() {
32945
33010
  return JSON.stringify(
@@ -32978,12 +33043,12 @@ function ngswConfig() {
32978
33043
  2
32979
33044
  );
32980
33045
  }
32981
- function manifest(d) {
33046
+ function manifest(distribution) {
32982
33047
  return JSON.stringify(
32983
33048
  {
32984
- name: d.title,
32985
- short_name: d.title,
32986
- description: `${d.title} \u2014 a LoomWeaver distribution.`,
33049
+ name: distribution.title,
33050
+ short_name: distribution.title,
33051
+ description: `${distribution.title} \u2014 a LoomWeaver distribution.`,
32987
33052
  start_url: "/",
32988
33053
  display: "standalone",
32989
33054
  background_color: "#ffffff",
@@ -33000,22 +33065,22 @@ var angularDistribution = {
33000
33065
  return distributionAmendments(resolveDistributionInput(input));
33001
33066
  },
33002
33067
  build(input) {
33003
- const d = resolveDistributionInput(input);
33068
+ const distribution = resolveDistributionInput(input);
33004
33069
  return {
33005
33070
  "src/main.ts": mainTs(),
33006
- "src/app/app.config.ts": appConfigTs(d),
33007
- ...d.withTests && {
33071
+ "src/app/app.config.ts": appConfigTs(distribution),
33072
+ ...distribution.withTests && {
33008
33073
  "src/app/app.config.spec.ts": appConfigSpec(),
33009
33074
  "src/app/app.spec.ts": appSpec()
33010
33075
  },
33011
33076
  "src/app/app.ts": appTs(),
33012
33077
  "src/app/app.html": appHtml(),
33013
- "src/index.html": indexHtml(d),
33014
- "src/styles.css": stylesCss(d),
33078
+ "src/index.html": indexHtml(distribution),
33079
+ "src/styles.css": stylesCss(distribution),
33015
33080
  "public/logo.svg": PLACEHOLDER_LOGO_SVG,
33016
- "public/manifest.webmanifest": manifest(d) + "\n",
33081
+ "public/manifest.webmanifest": manifest(distribution) + "\n",
33017
33082
  "ngsw-config.json": ngswConfig() + "\n",
33018
- "LOOMWEAVER.md": readme2(d)
33083
+ "LOOMWEAVER.md": readme2(distribution)
33019
33084
  };
33020
33085
  }
33021
33086
  };
@@ -33033,12 +33098,12 @@ function resolveAuthSourceInput(input) {
33033
33098
  bare: input.bare ?? false
33034
33099
  };
33035
33100
  }
33036
- function sessionPluginSymbol(a) {
33037
- return `${a.propertyName}SessionPlugin`;
33101
+ function sessionPluginSymbol(source) {
33102
+ return `${source.propertyName}SessionPlugin`;
33038
33103
  }
33039
- function moduleFile(a) {
33104
+ function moduleFile(source) {
33040
33105
  return `// Provider-neutral AuthSource. LoomWeaver owns no authentication \u2014 it only reacts to
33041
- // a session snapshot. Wire it with: provideAuthSource(() => ${a.propertyName}AuthSource()).
33106
+ // a session snapshot. Wire it with: provideAuthSource(() => ${source.propertyName}AuthSource()).
33042
33107
  // Replace the dev switcher below by mapping your product's real session onto an AuthSnapshot.
33043
33108
  import { signal, Signal } from '@angular/core';
33044
33109
  import { ANONYMOUS, AuthSnapshot } from '@loomweaver/plugin-sdk';
@@ -33059,52 +33124,40 @@ const ADMIN: AuthSnapshot = {
33059
33124
 
33060
33125
  const state = signal<AuthSnapshot>(ANONYMOUS);
33061
33126
 
33062
- export function ${a.propertyName}AuthSource(): Signal<AuthSnapshot> {
33127
+ export function ${source.propertyName}AuthSource(): Signal<AuthSnapshot> {
33063
33128
  return state.asReadonly();
33064
33129
  }
33065
33130
 
33066
- export function cycle${a.className}User(): void {
33067
- const current = state();
33068
- const next = !current.authenticated
33069
- ? USER
33070
- : current.roles.includes('admin')
33071
- ? ANONYMOUS
33072
- : ADMIN;
33073
- state.set(next);
33131
+ export function signIn${source.className}User(): void {
33132
+ state.set(USER);
33133
+ }
33134
+
33135
+ export function switch${source.className}Account(): void {
33136
+ state.set(state().roles.includes('admin') ? USER : ADMIN);
33137
+ }
33138
+
33139
+ export function signOut${source.className}User(): void {
33140
+ state.set(ANONYMOUS);
33074
33141
  }
33075
33142
  `;
33076
33143
  }
33077
- function pluginFile2(a) {
33144
+ function pluginFile2(source) {
33078
33145
  return `// The verbs a user needs to operate the stand-in session: sign in, switch the account, sign
33079
33146
  // out, from a rail item whose menu carries them. This is presentation for a product that has no
33080
33147
  // backend yet; nothing here protects anything. Delete it once your own session arrives.
33081
33148
  import type { Disposable, Plugin, PluginContext } from '@loomweaver/plugin-sdk';
33082
- import { cycle${a.className}User, ${a.propertyName}AuthSource } from './${a.name}-auth-source';
33149
+ import {
33150
+ ${source.propertyName}AuthSource,
33151
+ signIn${source.className}User,
33152
+ signOut${source.className}User,
33153
+ switch${source.className}Account,
33154
+ } from './${source.name}-auth-source';
33083
33155
 
33084
- const MENU = 'session.account/menu';
33085
- const snapshot = ${a.propertyName}AuthSource();
33156
+ const MENU = '${SESSION_PLUGIN_ID}.account/menu';
33157
+ const snapshot = ${source.propertyName}AuthSource();
33086
33158
 
33087
33159
  let drawn: Disposable[] = [];
33088
33160
 
33089
- function signIn(): void {
33090
- if (!snapshot().authenticated) {
33091
- cycle${a.className}User();
33092
- }
33093
- }
33094
-
33095
- function switchAccount(): void {
33096
- cycle${a.className}User();
33097
- if (!snapshot().authenticated) {
33098
- cycle${a.className}User();
33099
- }
33100
- }
33101
-
33102
- function signOut(): void {
33103
- while (snapshot().authenticated) {
33104
- cycle${a.className}User();
33105
- }
33106
- }
33107
-
33108
33161
  function initialsOf(name: string): string {
33109
33162
  return name
33110
33163
  .split(' ')
@@ -33123,7 +33176,7 @@ function draw(ctx: PluginContext): void {
33123
33176
  drawn = [
33124
33177
  ctx.registerRailItem({
33125
33178
  id: 'session.account',
33126
- rail: 'primary',
33179
+ rail: '${RAIL_REGION}',
33127
33180
  icon: 'account',
33128
33181
  title: current.authenticated ? name : 'session.signIn',
33129
33182
  anchor: 'bottom',
@@ -33168,10 +33221,10 @@ function draw(ctx: PluginContext): void {
33168
33221
  ];
33169
33222
  }
33170
33223
 
33171
- export const ${sessionPluginSymbol(a)}: Plugin = {
33224
+ export const ${sessionPluginSymbol(source)}: Plugin = {
33172
33225
  manifest: { id: 'session', name: 'Account', capabilities: ['contributions'] },
33173
33226
  activate(ctx) {
33174
- const then = (step: () => void) => () => {
33227
+ const andRedraw = (step: () => void) => () => {
33175
33228
  step();
33176
33229
  draw(ctx);
33177
33230
  };
@@ -33180,21 +33233,21 @@ export const ${sessionPluginSymbol(a)}: Plugin = {
33180
33233
  title: 'session.signIn',
33181
33234
  icon: 'account',
33182
33235
  access: { authenticated: false },
33183
- run: then(signIn),
33236
+ run: andRedraw(signIn${source.className}User),
33184
33237
  });
33185
33238
  ctx.registerCommand({
33186
33239
  id: 'session.switchAccount',
33187
33240
  title: 'session.switchAccount',
33188
33241
  icon: 'account',
33189
33242
  access: { authenticated: true },
33190
- run: then(switchAccount),
33243
+ run: andRedraw(switch${source.className}Account),
33191
33244
  });
33192
33245
  ctx.registerCommand({
33193
33246
  id: 'session.signOut',
33194
33247
  title: 'session.signOut',
33195
33248
  icon: 'signOut',
33196
33249
  access: { authenticated: true },
33197
- run: then(signOut),
33250
+ run: andRedraw(signOut${source.className}User),
33198
33251
  });
33199
33252
  draw(ctx);
33200
33253
  },
@@ -33207,9 +33260,14 @@ export const ${sessionPluginSymbol(a)}: Plugin = {
33207
33260
  };
33208
33261
  `;
33209
33262
  }
33210
- function indexFile2(a) {
33211
- return `export { cycle${a.className}User, ${a.propertyName}AuthSource } from './${a.name}-auth-source';
33212
- export { ${sessionPluginSymbol(a)} } from './${a.name}-session.plugin';
33263
+ function indexFile2(source) {
33264
+ return `export {
33265
+ ${source.propertyName}AuthSource,
33266
+ signIn${source.className}User,
33267
+ signOut${source.className}User,
33268
+ switch${source.className}Account,
33269
+ } from './${source.name}-auth-source';
33270
+ export { ${sessionPluginSymbol(source)} } from './${source.name}-session.plugin';
33213
33271
  `;
33214
33272
  }
33215
33273
  var I18N = {
@@ -33231,15 +33289,15 @@ var I18N = {
33231
33289
  var authSource = {
33232
33290
  id: "auth-source",
33233
33291
  build(input) {
33234
- const a = resolveAuthSourceInput(input);
33235
- const source = { [`${a.name}-auth-source.ts`]: moduleFile(a) };
33236
- if (a.bare) {
33237
- return source;
33292
+ const source = resolveAuthSourceInput(input);
33293
+ const moduleFiles = { [`${source.name}-auth-source.ts`]: moduleFile(source) };
33294
+ if (source.bare) {
33295
+ return moduleFiles;
33238
33296
  }
33239
33297
  return {
33240
- ...source,
33241
- [`${a.name}-session.plugin.ts`]: pluginFile2(a),
33242
- "index.ts": indexFile2(a),
33298
+ ...moduleFiles,
33299
+ [`${source.name}-session.plugin.ts`]: pluginFile2(source),
33300
+ "index.ts": indexFile2(source),
33243
33301
  "i18n/en.json": `${JSON.stringify(I18N.en, null, 2)}
33244
33302
  `,
33245
33303
  "i18n/de.json": `${JSON.stringify(I18N.de, null, 2)}
@@ -33257,14 +33315,14 @@ function resolveSettingsStoreInput(input) {
33257
33315
  }
33258
33316
  return { name: input.name, className: toPascalCase(input.name) };
33259
33317
  }
33260
- function moduleFile2(s) {
33318
+ function moduleFile2(store) {
33261
33319
  return `// A backend-backed settings store for the SETTINGS_STORE port. The platform
33262
33320
  // ships local defaults; the product persists settings against its own backend. Only genuine
33263
33321
  // settings flow through this port \u2014 working state stays on WORKING_STATE_STORE. Wire it with:
33264
- // provideSettingsStore(new ${s.className}SettingsStore()).
33322
+ // provideSettingsStore(new ${store.className}SettingsStore()).
33265
33323
  import { KeyValueStore } from '@loomweaver/shell';
33266
33324
 
33267
- export class ${s.className}SettingsStore implements KeyValueStore {
33325
+ export class ${store.className}SettingsStore implements KeyValueStore {
33268
33326
  constructor(private readonly baseUrl = '/api/settings') {}
33269
33327
 
33270
33328
  async get(key: string): Promise<string | undefined> {
@@ -33292,28 +33350,28 @@ export class ${s.className}SettingsStore implements KeyValueStore {
33292
33350
  var settingsStore = {
33293
33351
  id: "settings-store",
33294
33352
  build(input) {
33295
- const s = resolveSettingsStoreInput(input);
33296
- return { [`${s.name}-settings-store.ts`]: moduleFile2(s) };
33353
+ const store = resolveSettingsStoreInput(input);
33354
+ return { [`${store.name}-settings-store.ts`]: moduleFile2(store) };
33297
33355
  }
33298
33356
  };
33299
33357
 
33300
33358
  // ../devkit/src/recipes/theme/recipe.ts
33301
- var PRESETS = ["literal", "bootstrap"];
33359
+ var THEME_PRESETS = ["literal", "bootstrap"];
33302
33360
  function resolveThemeInput(input) {
33303
33361
  if (!isKebabId(input.name)) {
33304
33362
  throw new Error(`Theme name must be kebab-case (e.g. "midnight"); got "${input.name}".`);
33305
33363
  }
33306
33364
  const preset = input.preset ?? "literal";
33307
- if (!PRESETS.includes(preset)) {
33308
- throw new Error(`Unknown theme preset "${preset}"; expected one of ${PRESETS.join(", ")}.`);
33365
+ if (!THEME_PRESETS.includes(preset)) {
33366
+ throw new Error(`Unknown theme preset "${preset}"; expected one of ${THEME_PRESETS.join(", ")}.`);
33309
33367
  }
33310
33368
  return { name: input.name, title: toTitleCase(input.name), preset };
33311
33369
  }
33312
- function cssFile(t) {
33313
- return `/* ${t.title} theme \u2014 overrides LoomWeaver's --lw-* design tokens.
33370
+ function cssFile(theme2) {
33371
+ return `/* ${theme2.title} theme \u2014 overrides LoomWeaver's --lw-* design tokens.
33314
33372
  Import it AFTER the shell theme in your distribution styles.css:
33315
33373
  @import '@loomweaver/shell/styles/theme.css';
33316
- @import './themes/${t.name}.css';
33374
+ @import './themes/${theme2.name}.css';
33317
33375
  Declared in @layer lw-tenant-theme so it beats any plugin's ctx.contributeTheme
33318
33376
  (Product default < Plugin < Tenant). The --lw-* ladder flips in :root.dark, so override both. */
33319
33377
 
@@ -33348,15 +33406,15 @@ function cssFile(t) {
33348
33406
  }
33349
33407
  `;
33350
33408
  }
33351
- function bootstrapFile(t) {
33352
- return `/* ${t.title} theme \u2014 maps LoomWeaver's --lw-* design tokens onto Bootstrap 5.3's --bs-*.
33409
+ function bootstrapFile(theme2) {
33410
+ return `/* ${theme2.title} theme \u2014 maps LoomWeaver's --lw-* design tokens onto Bootstrap 5.3's --bs-*.
33353
33411
  Import it AFTER the shell theme in your distribution styles.css, and import Bootstrap itself
33354
33412
  INTO A LAYER \u2014 unlayered CSS outranks layered CSS whatever its specificity, so Bootstrap's
33355
33413
  Reboot (button { border-radius: 0 }) would otherwise beat our .lw-* component classes:
33356
33414
  @layer vendor;
33357
33415
  @import 'bootstrap/dist/css/bootstrap.css' layer(vendor);
33358
33416
  @import '@loomweaver/shell/styles/shell.css'; (or styles/theme.css if you run Tailwind)
33359
- @import './themes/${t.name}.css';
33417
+ @import './themes/${theme2.name}.css';
33360
33418
  Declared in @layer lw-tenant-theme so it beats any plugin's ctx.contributeTheme
33361
33419
  (Product default < Plugin < Tenant).
33362
33420
 
@@ -33431,9 +33489,9 @@ function bootstrapFile(t) {
33431
33489
  var theme = {
33432
33490
  id: "theme",
33433
33491
  build(input) {
33434
- const t = resolveThemeInput(input);
33435
- const content = t.preset === "bootstrap" ? bootstrapFile(t) : cssFile(t);
33436
- return { [`${t.name}.css`]: content };
33492
+ const theme2 = resolveThemeInput(input);
33493
+ const content = theme2.preset === "bootstrap" ? bootstrapFile(theme2) : cssFile(theme2);
33494
+ return { [`${theme2.name}.css`]: content };
33437
33495
  }
33438
33496
  };
33439
33497
 
@@ -33445,13 +33503,13 @@ function resolveLayoutInput(input) {
33445
33503
  }
33446
33504
  return { name, propertyName: toCamelCase(name) };
33447
33505
  }
33448
- function moduleFile3(l) {
33449
- return `// A base layout for the distribution. Pass it to provideLayout(${l.propertyName}Layout)
33450
- // in src/app/app.config.ts. Region ids are what contributions target \u2014 'primary' (rail) and 'status-bar' (bar) match
33506
+ function moduleFile3(layout2) {
33507
+ return `// A base layout for the distribution. Pass it to provideLayout(${layout2.propertyName}Layout)
33508
+ // in src/app/app.config.ts. Region ids are what contributions target \u2014 '${RAIL_REGION}' (rail) and '${STATUS_BAR_REGION}' (bar) match
33451
33509
  // the devkit weaver defaults, so a scaffolded weaver's rail + bar items land here.
33452
33510
  import { ShellLayout } from '@loomweaver/shell';
33453
33511
 
33454
- export const ${l.propertyName}Layout: ShellLayout = {
33512
+ export const ${layout2.propertyName}Layout: ShellLayout = {
33455
33513
  regions: [
33456
33514
  ${renderRegions(" ".repeat(4))}
33457
33515
  ],
@@ -33461,19 +33519,109 @@ ${renderRegions(" ".repeat(4))}
33461
33519
  var layout = {
33462
33520
  id: "layout",
33463
33521
  build(input) {
33464
- const l = resolveLayoutInput(input);
33465
- return { [`${l.name}-layout.ts`]: moduleFile3(l) };
33522
+ const layout2 = resolveLayoutInput(input);
33523
+ return { [`${layout2.name}-layout.ts`]: moduleFile3(layout2) };
33466
33524
  }
33467
33525
  };
33468
33526
 
33527
+ // ../devkit/src/lib/scaffolds/scaffold-values.ts
33528
+ function stringValue(values, name) {
33529
+ const value = values[name];
33530
+ return typeof value === "string" ? value : void 0;
33531
+ }
33532
+ function booleanValue(values, name) {
33533
+ const value = values[name];
33534
+ return typeof value === "boolean" ? value : void 0;
33535
+ }
33536
+ var PLACEMENT_OPTIONS = [
33537
+ {
33538
+ name: "directory",
33539
+ type: "string",
33540
+ description: "Project root, relative to the workspace root.",
33541
+ workspaceOnly: true
33542
+ },
33543
+ {
33544
+ name: "tags",
33545
+ type: "string",
33546
+ description: "Comma-separated Nx tags for the project.",
33547
+ workspaceOnly: true
33548
+ },
33549
+ {
33550
+ name: "prefix",
33551
+ type: "string",
33552
+ description: "Selector prefix for generated components and directives.",
33553
+ default: "lw",
33554
+ pattern: KEBAB_ID_PATTERN,
33555
+ workspaceOnly: true
33556
+ },
33557
+ {
33558
+ name: "unitTestRunner",
33559
+ type: "string",
33560
+ description: "Test wiring to emit. 'vitest' uses @nx/angular:unit-test; 'none' emits no test target.",
33561
+ choices: ["vitest", "none"],
33562
+ default: "vitest",
33563
+ workspaceOnly: true
33564
+ }
33565
+ ];
33566
+ var APP_OPTION = {
33567
+ name: "app",
33568
+ type: "string",
33569
+ description: "Application to drop into. Inferred when the workspace has exactly one.",
33570
+ workspaceOnly: true
33571
+ };
33572
+
33573
+ // ../devkit/src/recipes/angular-distribution/scaffold.ts
33574
+ function distributionInput(values) {
33575
+ return {
33576
+ name: stringValue(values, "name") ?? "",
33577
+ title: stringValue(values, "title"),
33578
+ directory: stringValue(values, "directory"),
33579
+ styles: stringValue(values, "styles") ?? "tailwind"
33580
+ };
33581
+ }
33582
+ var distributionScaffold = {
33583
+ name: "distribution",
33584
+ summary: "a runnable composition root that boots the shell",
33585
+ options: [
33586
+ {
33587
+ name: "name",
33588
+ type: "string",
33589
+ description: "Distribution name in kebab-case, e.g. 'acme-studio'.",
33590
+ required: true,
33591
+ pattern: KEBAB_ID_PATTERN
33592
+ },
33593
+ {
33594
+ name: "title",
33595
+ type: "string",
33596
+ description: "Product display title. Defaults to a title-cased name."
33597
+ },
33598
+ {
33599
+ name: "styles",
33600
+ type: "string",
33601
+ description: "Which stylesheet to emit. 'tailwind' compiles the shell's source theme and lets you write Tailwind utilities of your own; 'precompiled' imports the stylesheet we compiled, so the application needs no Tailwind and can be themed with Bootstrap or anything else.",
33602
+ choices: DISTRIBUTION_STYLES,
33603
+ default: "tailwind"
33604
+ },
33605
+ {
33606
+ name: "force",
33607
+ type: "boolean",
33608
+ description: "Compose into the application already at that path, replacing the bootstrap files this scaffold owns and merging its build targets. Without it an existing project is an error.",
33609
+ workspaceOnly: true
33610
+ },
33611
+ ...PLACEMENT_OPTIONS
33612
+ ],
33613
+ build: (values) => generate(angularDistribution, distributionInput(values)),
33614
+ amend: (values) => amendments(angularDistribution, distributionInput(values))
33615
+ };
33616
+
33469
33617
  // ../devkit/src/recipes/angular-weaver/amendments.ts
33470
33618
  function weaverAmendments(input, where) {
33471
- const w = resolveWeaverInput(input);
33472
- const packages = w.features.agent ? [
33619
+ const weaver = resolveWeaverInput(input);
33620
+ const packages = weaver.features.agent ? [
33473
33621
  {
33474
33622
  kind: "package",
33475
33623
  name: "@loomweaver/ag-ui",
33476
- version: `^${AG_UI_ADAPTER_VERSION}`
33624
+ version: `^${PLATFORM_VERSION}`
33477
33625
  },
33478
33626
  {
33479
33627
  kind: "package",
@@ -33495,25 +33643,145 @@ function weaverAmendments(input, where) {
33495
33643
  glob: "**/*.json",
33496
33644
  input: joinProjectPath(directory, "src/lib/i18n"),
33497
33645
  from: "workspace",
33498
- output: `i18n/${w.id}`
33646
+ output: `i18n/${weaver.id}`
33499
33647
  }
33500
33648
  ]
33501
33649
  },
33502
33650
  { kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
33503
33651
  {
33504
33652
  kind: "compose-plugin",
33505
- id: w.id,
33506
- symbol: `${w.propertyName}Plugin`,
33507
- capabilities: w.capabilities,
33653
+ id: weaver.id,
33654
+ symbol: `${weaver.propertyName}Plugin`,
33655
+ capabilities: weaver.capabilities,
33508
33656
  sourceRoot: joinProjectPath(directory, "src")
33509
33657
  }
33510
33658
  ];
33511
33659
  }
33512
33660
 
33513
- // ../devkit/src/recipes/auth-source/recipe-amendments.ts
33661
+ // ../devkit/src/recipes/angular-weaver/scaffold.ts
33662
+ function weaverInput(values) {
33663
+ return {
33664
+ id: stringValue(values, "id") ?? "",
33665
+ name: stringValue(values, "name"),
33666
+ prefix: stringValue(values, "prefix"),
33667
+ importPath: stringValue(values, "importPath"),
33668
+ features: {
33669
+ command: booleanValue(values, "command"),
33670
+ shortcut: stringValue(values, "shortcut"),
33671
+ menu: stringValue(values, "menu"),
33672
+ barItem: booleanValue(values, "barItem"),
33673
+ settings: booleanValue(values, "settings"),
33674
+ about: booleanValue(values, "about"),
33675
+ instanceable: booleanValue(values, "instanceable"),
33676
+ container: booleanValue(values, "container"),
33677
+ agent: booleanValue(values, "agent"),
33678
+ access: stringValue(values, "access"),
33679
+ spec: booleanValue(values, "spec")
33680
+ }
33681
+ };
33682
+ }
33683
+ var weaverScaffold = {
33684
+ name: "weaver",
33685
+ summary: "an Angular in-process plugin: manifest, surface, rail item, i18n, test",
33686
+ options: [
33687
+ {
33688
+ name: "id",
33689
+ type: "string",
33690
+ description: "Plugin id in kebab-case, e.g. 'notes'.",
33691
+ required: true,
33692
+ pattern: KEBAB_ID_PATTERN
33693
+ },
33694
+ {
33695
+ name: "name",
33696
+ type: "string",
33697
+ description: "Human-readable name. Defaults to a title-cased id."
33698
+ },
33699
+ {
33700
+ name: "command",
33701
+ type: "boolean",
33702
+ description: "Also scaffold a registered command the weaver can trigger.",
33703
+ default: false
33704
+ },
33705
+ {
33706
+ name: "shortcut",
33707
+ type: "string",
33708
+ description: "Keyboard chord for the command, e.g. 'mod+shift+k'. Implies --command."
33709
+ },
33710
+ {
33711
+ name: "menu",
33712
+ type: "string",
33713
+ description: "Hook a menu item into a slot, e.g. 'content/tab/context'. Implies --command."
33714
+ },
33715
+ {
33716
+ name: "barItem",
33717
+ type: "boolean",
33718
+ description: "Also add a status-bar button that triggers the command. Implies --command.",
33719
+ default: false
33720
+ },
33721
+ {
33722
+ name: "settings",
33723
+ type: "boolean",
33724
+ description: "Also scaffold a settings section with signal-backed value owners.",
33725
+ default: false
33726
+ },
33727
+ {
33728
+ name: "about",
33729
+ type: "boolean",
33730
+ description: "Also scaffold an About dialog that reads ctx.host, plus a bottom rail item.",
33731
+ default: false
33732
+ },
33733
+ {
33734
+ name: "instanceable",
33735
+ type: "boolean",
33736
+ description: "Dock the surface and give it named saved instances, each with its own view state. Not combinable with --container: named instances exist only for a docked, non-routable surface.",
33737
+ default: false
33738
+ },
33739
+ {
33740
+ name: "container",
33741
+ type: "boolean",
33742
+ description: "Make the surface a container: a routable tab at '<id>/:id' holding a nested pane tree of child surfaces, which the host draws. Not combinable with --instanceable.",
33743
+ default: false
33744
+ },
33745
+ {
33746
+ name: "agent",
33747
+ type: "boolean",
33748
+ description: "Also scaffold the connection that lets an AG-UI agent run the commands this workbench offers: a docked panel, the seam where a call is decided before it runs, and a local stand-in that speaks the protocol so the whole path works on the first serve. Implies --command.",
33749
+ default: false
33750
+ },
33751
+ {
33752
+ name: "access",
33753
+ type: "string",
33754
+ description: "Auth-gate the surface and rail item: 'authenticated', 'anonymous' or 'role:<name>'."
33755
+ },
33756
+ {
33757
+ name: "spec",
33758
+ type: "boolean",
33759
+ description: "Generate a starter unit test. Use --no-spec to skip it.",
33760
+ default: true
33761
+ },
33762
+ {
33763
+ name: "projectName",
33764
+ type: "string",
33765
+ description: "Nx project name. Defaults to '<id>-weaver'.",
33766
+ workspaceOnly: true
33767
+ },
33768
+ {
33769
+ name: "importPath",
33770
+ type: "string",
33771
+ description: "Import path for the workspace alias. Defaults to the workspace scope plus the project name.",
33772
+ workspaceOnly: true
33773
+ },
33774
+ APP_OPTION,
33775
+ ...PLACEMENT_OPTIONS
33776
+ ],
33777
+ build: (values) => generate(angularWeaver, weaverInput(values)),
33778
+ amend: (values) => weaverAmendments(weaverInput(values), stringValue(values, "directory"))
33779
+ };
33780
+
33781
+ // ../devkit/src/recipes/auth-source/amendments.ts
33514
33782
  function authSourceAmendments(input, where) {
33515
- const a = resolveAuthSourceInput(input);
33516
- if (a.bare || where === void 0 || where === "") {
33783
+ const source = resolveAuthSourceInput(input);
33784
+ if (source.bare || where === void 0 || where === "") {
33517
33785
  return [];
33518
33786
  }
33519
33787
  const directory = normalizeProjectRoot(where);
@@ -33533,14 +33801,14 @@ function authSourceAmendments(input, where) {
33533
33801
  {
33534
33802
  kind: "compose-plugin",
33535
33803
  id: SESSION_PLUGIN_ID,
33536
- symbol: sessionPluginSymbol(a),
33804
+ symbol: sessionPluginSymbol(source),
33537
33805
  capabilities: ["contributions"],
33538
33806
  sourceRoot: directory,
33539
33807
  providers: [
33540
33808
  {
33541
- line: `provideAuthSource(() => ${a.propertyName}AuthSource()),`,
33809
+ line: `provideAuthSource(() => ${source.propertyName}AuthSource()),`,
33542
33810
  shell: ["provideAuthSource"],
33543
- own: [`${a.propertyName}AuthSource`],
33811
+ own: [`${source.propertyName}AuthSource`],
33544
33812
  unless: "provideAuthSource("
33545
33813
  },
33546
33814
  {
@@ -33558,325 +33826,135 @@ function authSourceAmendments(input, where) {
33558
33826
  ];
33559
33827
  }
33560
33828
 
33561
- // ../devkit/src/lib/scaffolds/inputs.ts
33562
- function weaverInput(values) {
33563
- return {
33564
- id: str(values, "id") ?? "",
33565
- name: str(values, "name"),
33566
- prefix: str(values, "prefix"),
33567
- importPath: str(values, "importPath"),
33568
- features: {
33569
- command: bool(values, "command"),
33570
- shortcut: str(values, "shortcut"),
33571
- menu: str(values, "menu"),
33572
- barItem: bool(values, "barItem"),
33573
- settings: bool(values, "settings"),
33574
- about: bool(values, "about"),
33575
- instanceable: bool(values, "instanceable"),
33576
- container: bool(values, "container"),
33577
- agent: bool(values, "agent"),
33578
- access: str(values, "access"),
33579
- spec: bool(values, "spec")
33580
- }
33581
- };
33582
- }
33583
- function distributionInput(values) {
33584
- return {
33585
- name: str(values, "name") ?? "",
33586
- title: str(values, "title"),
33587
- directory: str(values, "directory"),
33588
- styles: str(values, "styles") ?? "tailwind"
33589
- };
33829
+ // ../devkit/src/recipes/auth-source/scaffold.ts
33830
+ function authSourceInput(values) {
33831
+ return { name: stringValue(values, "name") ?? "", bare: booleanValue(values, "bare") };
33590
33832
  }
33833
+ var authSourceScaffold = {
33834
+ name: "auth-source",
33835
+ summary: "a stand-in session a user can operate: the AuthSource, the sign-in, switch and sign-out verbs in the rail, composed in",
33836
+ options: [
33837
+ {
33838
+ name: "name",
33839
+ type: "string",
33840
+ description: "Source name in kebab-case, e.g. 'dev'.",
33841
+ required: true,
33842
+ pattern: KEBAB_ID_PATTERN
33843
+ },
33844
+ {
33845
+ name: "bare",
33846
+ type: "boolean",
33847
+ description: "Write the AuthSource alone, without the verbs, for a product that maps a session of its own onto it.",
33848
+ default: false
33849
+ },
33850
+ APP_OPTION,
33851
+ {
33852
+ name: "directory",
33853
+ type: "string",
33854
+ description: "Where the files land, relative to the workspace root.",
33855
+ workspaceOnly: true
33856
+ }
33857
+ ],
33858
+ build: (values) => generate(authSource, authSourceInput(values)),
33859
+ amend: (values) => authSourceAmendments(authSourceInput(values), stringValue(values, "directory"))
33860
+ };
33591
33861
 
33592
- // ../devkit/src/lib/scaffolds/scaffolds.ts
33593
- function str(values, name) {
33594
- const value = values[name];
33595
- return typeof value === "string" ? value : void 0;
33596
- }
33597
- function bool(values, name) {
33598
- const value = values[name];
33599
- return typeof value === "boolean" ? value : void 0;
33600
- }
33601
- var ID_PATTERN = "^[a-z][a-z0-9]*(-[a-z0-9]+)*$";
33602
- var PLACEMENT_OPTIONS = [
33603
- {
33604
- name: "directory",
33605
- type: "string",
33606
- description: "Project root, relative to the workspace root.",
33607
- workspaceOnly: true
33608
- },
33609
- {
33610
- name: "tags",
33611
- type: "string",
33612
- description: "Comma-separated Nx tags for the project.",
33613
- workspaceOnly: true
33614
- },
33615
- {
33616
- name: "prefix",
33617
- type: "string",
33618
- description: "Selector prefix for generated components and directives.",
33619
- default: "lw",
33620
- pattern: ID_PATTERN,
33621
- workspaceOnly: true
33622
- },
33623
- {
33624
- name: "unitTestRunner",
33625
- type: "string",
33626
- description: "Test wiring to emit. 'vitest' uses @nx/angular:unit-test; 'none' emits no test target.",
33627
- choices: ["vitest", "none"],
33628
- default: "vitest",
33629
- workspaceOnly: true
33630
- }
33631
- ];
33632
- var APP_OPTION = {
33633
- name: "app",
33634
- type: "string",
33635
- description: "Application to drop into. Inferred when the workspace has exactly one.",
33636
- workspaceOnly: true
33862
+ // ../devkit/src/recipes/frame-plugin/scaffold.ts
33863
+ var framePluginScaffold = {
33864
+ name: "frame-plugin",
33865
+ summary: "a framework-agnostic iframe plugin (Penpal + the frame UI kit)",
33866
+ options: [
33867
+ {
33868
+ name: "id",
33869
+ type: "string",
33870
+ description: "Plugin id in kebab-case, e.g. 'notes'.",
33871
+ required: true,
33872
+ pattern: KEBAB_ID_PATTERN
33873
+ },
33874
+ {
33875
+ name: "name",
33876
+ type: "string",
33877
+ description: "Human-readable name. Defaults to a title-cased id."
33878
+ },
33879
+ APP_OPTION
33880
+ ],
33881
+ build: (values) => generate(framePlugin, {
33882
+ id: stringValue(values, "id") ?? "",
33883
+ name: stringValue(values, "name")
33884
+ })
33637
33885
  };
33638
- function authSourceInput(values) {
33639
- return { name: str(values, "name") ?? "", bare: bool(values, "bare") };
33640
- }
33886
+
33887
+ // ../devkit/src/recipes/layout/scaffold.ts
33888
+ var layoutScaffold = {
33889
+ name: "layout",
33890
+ summary: "a ShellLayout with the regions a weaver expects",
33891
+ options: [
33892
+ {
33893
+ name: "name",
33894
+ type: "string",
33895
+ description: "Layout name. Defaults to a base layout."
33896
+ },
33897
+ APP_OPTION
33898
+ ],
33899
+ build: (values) => generate(layout, { name: stringValue(values, "name") })
33900
+ };
33901
+
33902
+ // ../devkit/src/recipes/settings-store/scaffold.ts
33903
+ var settingsStoreScaffold = {
33904
+ name: "settings-store",
33905
+ summary: "a settings-store implementation backed by your API",
33906
+ options: [
33907
+ {
33908
+ name: "name",
33909
+ type: "string",
33910
+ description: "Store name in kebab-case, e.g. 'backend'.",
33911
+ required: true,
33912
+ pattern: KEBAB_ID_PATTERN
33913
+ },
33914
+ APP_OPTION
33915
+ ],
33916
+ build: (values) => generate(settingsStore, { name: stringValue(values, "name") ?? "" })
33917
+ };
33918
+
33919
+ // ../devkit/src/recipes/theme/scaffold.ts
33920
+ var themeScaffold = {
33921
+ name: "theme",
33922
+ summary: "a token-override stylesheet in @layer lw-tenant-theme",
33923
+ options: [
33924
+ {
33925
+ name: "name",
33926
+ type: "string",
33927
+ description: "Theme name in kebab-case, e.g. 'midnight'.",
33928
+ required: true,
33929
+ pattern: KEBAB_ID_PATTERN
33930
+ },
33931
+ {
33932
+ name: "preset",
33933
+ type: "string",
33934
+ description: "Where the token values come from. 'literal' writes editable colours; 'bootstrap' maps them onto Bootstrap 5.3's --bs-* variables, which makes the shell follow your Bootstrap theme live.",
33935
+ choices: THEME_PRESETS,
33936
+ default: "literal"
33937
+ },
33938
+ APP_OPTION
33939
+ ],
33940
+ build: (values) => generate(theme, {
33941
+ name: stringValue(values, "name") ?? "",
33942
+ preset: stringValue(values, "preset") ?? "literal"
33943
+ })
33944
+ };
33945
+
33946
+ // ../devkit/src/lib/scaffolds/scaffolds.ts
33641
33947
  var SCAFFOLDS = [
33642
- {
33643
- name: "weaver",
33644
- summary: "an Angular in-process plugin: manifest, surface, rail item, i18n, test",
33645
- options: [
33646
- {
33647
- name: "id",
33648
- type: "string",
33649
- description: "Plugin id in kebab-case, e.g. 'notes'.",
33650
- required: true,
33651
- pattern: ID_PATTERN
33652
- },
33653
- {
33654
- name: "name",
33655
- type: "string",
33656
- description: "Human-readable name. Defaults to a title-cased id."
33657
- },
33658
- {
33659
- name: "command",
33660
- type: "boolean",
33661
- description: "Also scaffold a registered command the weaver can trigger.",
33662
- default: false
33663
- },
33664
- {
33665
- name: "shortcut",
33666
- type: "string",
33667
- description: "Keyboard chord for the command, e.g. 'mod+shift+k'. Implies --command."
33668
- },
33669
- {
33670
- name: "menu",
33671
- type: "string",
33672
- description: "Hook a menu item into a slot, e.g. 'content/tab/context'. Implies --command."
33673
- },
33674
- {
33675
- name: "barItem",
33676
- type: "boolean",
33677
- description: "Also add a status-bar button that triggers the command. Implies --command.",
33678
- default: false
33679
- },
33680
- {
33681
- name: "settings",
33682
- type: "boolean",
33683
- description: "Also scaffold a settings section with signal-backed value owners.",
33684
- default: false
33685
- },
33686
- {
33687
- name: "about",
33688
- type: "boolean",
33689
- description: "Also scaffold an About dialog that reads ctx.host, plus a bottom rail item.",
33690
- default: false
33691
- },
33692
- {
33693
- name: "instanceable",
33694
- type: "boolean",
33695
- description: "Dock the surface and give it named saved instances, each with its own view state. Not combinable with --container: named instances exist only for a docked, non-routable surface.",
33696
- default: false
33697
- },
33698
- {
33699
- name: "container",
33700
- type: "boolean",
33701
- description: "Make the surface a container: a routable tab at '<id>/:id' holding a nested pane tree of child surfaces, which the host draws. Not combinable with --instanceable.",
33702
- default: false
33703
- },
33704
- {
33705
- name: "agent",
33706
- type: "boolean",
33707
- description: "Also scaffold the connection that lets an AG-UI agent run the commands this workbench offers: a docked panel, the seam where a call is decided before it runs, and a local stand-in that speaks the protocol so the whole path works on the first serve. Implies --command.",
33708
- default: false
33709
- },
33710
- {
33711
- name: "access",
33712
- type: "string",
33713
- description: "Auth-gate the surface and rail item: 'authenticated', 'anonymous' or 'role:<name>'."
33714
- },
33715
- {
33716
- name: "spec",
33717
- type: "boolean",
33718
- description: "Generate a starter unit test. Use --no-spec to skip it.",
33719
- default: true
33720
- },
33721
- {
33722
- name: "projectName",
33723
- type: "string",
33724
- description: "Nx project name. Defaults to '<id>-weaver'.",
33725
- workspaceOnly: true
33726
- },
33727
- {
33728
- name: "importPath",
33729
- type: "string",
33730
- description: "Import path for the workspace alias. Defaults to the workspace scope plus the project name.",
33731
- workspaceOnly: true
33732
- },
33733
- APP_OPTION,
33734
- ...PLACEMENT_OPTIONS
33735
- ],
33736
- build: (values) => generate(angularWeaver, weaverInput(values)),
33737
- amend: (values) => weaverAmendments(weaverInput(values), str(values, "directory"))
33738
- },
33739
- {
33740
- name: "frame-plugin",
33741
- summary: "a framework-agnostic iframe plugin (Penpal + the frame UI kit)",
33742
- options: [
33743
- {
33744
- name: "id",
33745
- type: "string",
33746
- description: "Plugin id in kebab-case, e.g. 'notes'.",
33747
- required: true,
33748
- pattern: ID_PATTERN
33749
- },
33750
- {
33751
- name: "name",
33752
- type: "string",
33753
- description: "Human-readable name. Defaults to a title-cased id."
33754
- },
33755
- APP_OPTION
33756
- ],
33757
- build: (values) => generate(framePlugin, {
33758
- id: str(values, "id") ?? "",
33759
- name: str(values, "name")
33760
- })
33761
- },
33762
- {
33763
- name: "distribution",
33764
- summary: "a runnable composition root that boots the shell",
33765
- options: [
33766
- {
33767
- name: "name",
33768
- type: "string",
33769
- description: "Distribution name in kebab-case, e.g. 'acme-studio'.",
33770
- required: true,
33771
- pattern: ID_PATTERN
33772
- },
33773
- {
33774
- name: "title",
33775
- type: "string",
33776
- description: "Product display title. Defaults to a title-cased name."
33777
- },
33778
- {
33779
- name: "styles",
33780
- type: "string",
33781
- description: "Which stylesheet to emit. 'tailwind' compiles the shell's source theme and lets you write Tailwind utilities of your own; 'precompiled' imports the stylesheet we compiled, so the application needs no Tailwind and can be themed with Bootstrap or anything else.",
33782
- choices: ["tailwind", "precompiled"],
33783
- default: "tailwind"
33784
- },
33785
- {
33786
- name: "force",
33787
- type: "boolean",
33788
- description: "Compose into the application already at that path, replacing the bootstrap files this scaffold owns and merging its build targets. Without it an existing project is an error.",
33789
- workspaceOnly: true
33790
- },
33791
- ...PLACEMENT_OPTIONS
33792
- ],
33793
- build: (values) => generate(angularDistribution, distributionInput(values)),
33794
- amend: (values) => amendments(angularDistribution, distributionInput(values))
33795
- },
33796
- {
33797
- name: "auth-source",
33798
- summary: "a stand-in session a user can operate: the AuthSource, the sign-in, switch and sign-out verbs in the rail, composed in",
33799
- options: [
33800
- {
33801
- name: "name",
33802
- type: "string",
33803
- description: "Source name in kebab-case, e.g. 'dev'.",
33804
- required: true,
33805
- pattern: ID_PATTERN
33806
- },
33807
- {
33808
- name: "bare",
33809
- type: "boolean",
33810
- description: "Write the AuthSource alone, without the verbs, for a product that maps a session of its own onto it.",
33811
- default: false
33812
- },
33813
- APP_OPTION,
33814
- {
33815
- name: "directory",
33816
- type: "string",
33817
- description: "Where the files land, relative to the workspace root.",
33818
- workspaceOnly: true
33819
- }
33820
- ],
33821
- build: (values) => generate(authSource, authSourceInput(values)),
33822
- amend: (values) => authSourceAmendments(authSourceInput(values), str(values, "directory"))
33823
- },
33824
- {
33825
- name: "settings-store",
33826
- summary: "a settings-store implementation backed by your API",
33827
- options: [
33828
- {
33829
- name: "name",
33830
- type: "string",
33831
- description: "Store name in kebab-case, e.g. 'backend'.",
33832
- required: true,
33833
- pattern: ID_PATTERN
33834
- },
33835
- APP_OPTION
33836
- ],
33837
- build: (values) => generate(settingsStore, { name: str(values, "name") ?? "" })
33838
- },
33839
- {
33840
- name: "theme",
33841
- summary: "a token-override stylesheet in @layer lw-tenant-theme",
33842
- options: [
33843
- {
33844
- name: "name",
33845
- type: "string",
33846
- description: "Theme name in kebab-case, e.g. 'midnight'.",
33847
- required: true,
33848
- pattern: ID_PATTERN
33849
- },
33850
- {
33851
- name: "preset",
33852
- type: "string",
33853
- description: "Where the token values come from. 'literal' writes editable colours; 'bootstrap' maps them onto Bootstrap 5.3's --bs-* variables, which makes the shell follow your Bootstrap theme live.",
33854
- choices: ["literal", "bootstrap"],
33855
- default: "literal"
33856
- },
33857
- APP_OPTION
33858
- ],
33859
- build: (values) => generate(theme, {
33860
- name: str(values, "name") ?? "",
33861
- preset: str(values, "preset") ?? "literal"
33862
- })
33863
- },
33864
- {
33865
- name: "layout",
33866
- summary: "a ShellLayout with the regions a weaver expects",
33867
- options: [
33868
- {
33869
- name: "name",
33870
- type: "string",
33871
- description: "Layout name. Defaults to a base layout."
33872
- },
33873
- APP_OPTION
33874
- ],
33875
- build: (values) => generate(layout, { name: str(values, "name") })
33876
- }
33948
+ weaverScaffold,
33949
+ framePluginScaffold,
33950
+ distributionScaffold,
33951
+ authSourceScaffold,
33952
+ settingsStoreScaffold,
33953
+ themeScaffold,
33954
+ layoutScaffold
33877
33955
  ];
33878
33956
 
33879
- // ../devkit/src/lib/scaffolds/surface.ts
33957
+ // ../devkit/src/lib/scaffolds/adapter-options.ts
33880
33958
  function portableOptions(scaffold2) {
33881
33959
  return scaffold2.options.filter((option) => !option.workspaceOnly);
33882
33960
  }
@@ -33938,41 +34016,50 @@ var CATALOG_ENTRY_KEYS = [
33938
34016
  "readmeUrl"
33939
34017
  ];
33940
34018
  var SAME_ORIGIN_FIELDS = ["entryUrl", "iconUrl", "readmeUrl"];
33941
- function at(index, field) {
34019
+ function pathOf(index, field) {
33942
34020
  return field ? `catalog[${index}].${field}` : `catalog[${index}]`;
33943
34021
  }
33944
34022
  function isPlainObject3(raw) {
33945
34023
  return typeof raw === "object" && raw !== null && !Array.isArray(raw);
33946
34024
  }
33947
- function urlFinding(value, index, field, required2) {
34025
+ function requiredUrlFinding(value, index, field) {
33948
34026
  if (typeof value !== "string" || value.length === 0) {
33949
- return required2 ? {
34027
+ return {
33950
34028
  level: "error",
33951
34029
  code: "catalog.entryUrl",
33952
- message: `${at(index, field)} must be a non-empty string; the host drops the whole entry without it.`,
33953
- path: at(index, field)
33954
- } : {
34030
+ message: `${pathOf(index, field)} must be a non-empty string; the host drops the whole entry without it.`,
34031
+ path: pathOf(index, field)
34032
+ };
34033
+ }
34034
+ return schemeFinding(value, index, field, "drops the entry");
34035
+ }
34036
+ function optionalUrlFinding(value, index, field) {
34037
+ if (typeof value !== "string" || value.length === 0) {
34038
+ return {
33955
34039
  level: "warning",
33956
34040
  code: "catalog.url.empty",
33957
- message: `${at(index, field)} is present but not a non-empty string, so the host ignores it.`,
33958
- path: at(index, field)
34041
+ message: `${pathOf(index, field)} is present but not a non-empty string, so the host ignores it.`,
34042
+ path: pathOf(index, field)
33959
34043
  };
33960
34044
  }
34045
+ return schemeFinding(value, index, field, "ignores the field");
34046
+ }
34047
+ function schemeFinding(value, index, field, refusal) {
33961
34048
  const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value)?.[1]?.toLowerCase();
33962
34049
  if (scheme && scheme !== "http" && scheme !== "https") {
33963
34050
  return {
33964
34051
  level: "error",
33965
34052
  code: "catalog.url.scheme",
33966
- message: `${at(index, field)} uses the "${scheme}:" scheme. The host accepts same-origin http(s) URLs only${required2 ? " and drops the entry" : " and ignores the field"}.`,
33967
- path: at(index, field)
34053
+ message: `${pathOf(index, field)} uses the "${scheme}:" scheme. The host accepts same-origin http(s) URLs only and ${refusal}.`,
34054
+ path: pathOf(index, field)
33968
34055
  };
33969
34056
  }
33970
34057
  if (scheme) {
33971
34058
  return {
33972
34059
  level: "warning",
33973
34060
  code: "catalog.url.absolute",
33974
- message: `${at(index, field)} is absolute. The host requires same-origin, so this holds only while it matches the origin the app is served from; a root-relative path is same-origin by construction.`,
33975
- path: at(index, field)
34061
+ message: `${pathOf(index, field)} is absolute. The host requires same-origin, so this holds only while it matches the origin the app is served from; a root-relative path is same-origin by construction.`,
34062
+ path: pathOf(index, field)
33976
34063
  };
33977
34064
  }
33978
34065
  return void 0;
@@ -33983,8 +34070,8 @@ function validateCapabilities2(value, index, known) {
33983
34070
  {
33984
34071
  level: "warning",
33985
34072
  code: "catalog.capabilities.missing",
33986
- message: `${at(index)} declares no capabilities. Accepting the install dialog grants exactly what is declared, so the plugin will be denied everything at runtime.`,
33987
- path: at(index, "capabilities")
34073
+ message: `${pathOf(index)} declares no capabilities. Accepting the install dialog grants exactly what is declared, so the plugin will be denied everything at runtime.`,
34074
+ path: pathOf(index, "capabilities")
33988
34075
  }
33989
34076
  ];
33990
34077
  }
@@ -33993,8 +34080,8 @@ function validateCapabilities2(value, index, known) {
33993
34080
  {
33994
34081
  level: "error",
33995
34082
  code: "catalog.capabilities",
33996
- message: `${at(index, "capabilities")} must be an array; the host ignores any other shape and grants nothing.`,
33997
- path: at(index, "capabilities")
34083
+ message: `${pathOf(index, "capabilities")} must be an array; the host ignores any other shape and grants nothing.`,
34084
+ path: pathOf(index, "capabilities")
33998
34085
  }
33999
34086
  ];
34000
34087
  }
@@ -34004,8 +34091,8 @@ function validateCapabilities2(value, index, known) {
34004
34091
  findings.push({
34005
34092
  level: "error",
34006
34093
  code: "catalog.capability.unknown",
34007
- message: `${at(index, "capabilities")} contains ${JSON.stringify(capability)}, which the host filters out silently \u2014 the plugin then throws CapabilityError at runtime. Known: ${known.join(", ")}.`,
34008
- path: at(index, "capabilities")
34094
+ message: `${pathOf(index, "capabilities")} contains ${JSON.stringify(capability)}, which the host filters out silently \u2014 the plugin then throws CapabilityError at runtime. Known: ${known.join(", ")}.`,
34095
+ path: pathOf(index, "capabilities")
34009
34096
  });
34010
34097
  }
34011
34098
  }
@@ -34017,58 +34104,69 @@ function validateEntry(raw, index, known) {
34017
34104
  {
34018
34105
  level: "error",
34019
34106
  code: "catalog.entry",
34020
- message: `${at(index)} is not an object, so the host drops it.`,
34021
- path: at(index)
34107
+ message: `${pathOf(index)} is not an object, so the host drops it.`,
34108
+ path: pathOf(index)
34022
34109
  }
34023
34110
  ];
34024
34111
  }
34025
34112
  return [
34026
- ...validateEntryIdentity(raw, index),
34113
+ ...validateId(raw, index),
34114
+ ...validateSameOriginUrls(raw, index),
34115
+ ...validateNameAndVersion(raw, index),
34027
34116
  ...validateCapabilities2(raw["capabilities"], index, known),
34028
34117
  ...validateEntryMetadata(raw, index),
34029
34118
  ...validateEntryKeys(raw, index)
34030
34119
  ];
34031
34120
  }
34032
- function validateEntryIdentity(raw, index) {
34033
- const findings = [];
34034
- if (typeof raw["id"] !== "string" || raw["id"].length === 0) {
34035
- findings.push({
34121
+ function validateId(raw, index) {
34122
+ return typeof raw["id"] === "string" && raw["id"].length > 0 ? [] : [
34123
+ {
34036
34124
  level: "error",
34037
34125
  code: "catalog.id",
34038
- message: `${at(index, "id")} must be a non-empty string; the host drops the whole entry without it.`,
34039
- path: at(index, "id")
34040
- });
34041
- }
34042
- for (const field of SAME_ORIGIN_FIELDS) {
34043
- if (field !== "entryUrl" && raw[field] === void 0) {
34044
- continue;
34126
+ message: `${pathOf(index, "id")} must be a non-empty string; the host drops the whole entry without it.`,
34127
+ path: pathOf(index, "id")
34045
34128
  }
34046
- const finding = urlFinding(raw[field], index, field, field === "entryUrl");
34129
+ ];
34130
+ }
34131
+ function validateSameOriginUrls(raw, index) {
34132
+ const findings = [];
34133
+ for (const field of SAME_ORIGIN_FIELDS) {
34134
+ const finding = sameOriginUrlFinding(raw, index, field);
34047
34135
  if (finding) {
34048
34136
  findings.push(finding);
34049
34137
  }
34050
34138
  }
34139
+ return findings;
34140
+ }
34141
+ function sameOriginUrlFinding(raw, index, field) {
34142
+ if (field === "entryUrl") {
34143
+ return requiredUrlFinding(raw[field], index, field);
34144
+ }
34145
+ return raw[field] === void 0 ? void 0 : optionalUrlFinding(raw[field], index, field);
34146
+ }
34147
+ function validateNameAndVersion(raw, index) {
34148
+ const findings = [];
34051
34149
  if (raw["name"] === void 0) {
34052
34150
  findings.push({
34053
34151
  level: "warning",
34054
34152
  code: "catalog.name.missing",
34055
- message: `${at(index)} has no name, so the store falls back to showing the id.`,
34056
- path: at(index, "name")
34153
+ message: `${pathOf(index)} has no name, so the store falls back to showing the id.`,
34154
+ path: pathOf(index, "name")
34057
34155
  });
34058
34156
  } else if (typeof raw["name"] !== "string" || raw["name"].length === 0) {
34059
34157
  findings.push({
34060
34158
  level: "warning",
34061
34159
  code: "catalog.name",
34062
- message: `${at(index, "name")} is not a non-empty string, so the store falls back to showing the id.`,
34063
- path: at(index, "name")
34160
+ message: `${pathOf(index, "name")} is not a non-empty string, so the store falls back to showing the id.`,
34161
+ path: pathOf(index, "name")
34064
34162
  });
34065
34163
  }
34066
34164
  if (raw["version"] === void 0) {
34067
34165
  findings.push({
34068
34166
  level: "warning",
34069
34167
  code: "catalog.version.missing",
34070
- message: `${at(index)} carries no version. Update detection compares catalog versions, so the store can never offer an update and republishing the plugin will not respawn it for anyone who already installed it.`,
34071
- path: at(index, "version")
34168
+ message: `${pathOf(index)} carries no version. Update detection compares catalog versions, so the store can never offer an update and republishing the plugin will not respawn it for anyone who already installed it.`,
34169
+ path: pathOf(index, "version")
34072
34170
  });
34073
34171
  }
34074
34172
  return findings;
@@ -34079,24 +34177,24 @@ function validateEntryMetadata(raw, index) {
34079
34177
  findings.push({
34080
34178
  level: "warning",
34081
34179
  code: "catalog.downloads",
34082
- message: `${at(index, "downloads")} must be a non-negative number, so the host ignores it.`,
34083
- path: at(index, "downloads")
34180
+ message: `${pathOf(index, "downloads")} must be a non-negative number, so the host ignores it.`,
34181
+ path: pathOf(index, "downloads")
34084
34182
  });
34085
34183
  }
34086
34184
  if (raw["updated"] !== void 0 && !isRenderableDate(raw["updated"])) {
34087
34185
  findings.push({
34088
34186
  level: "warning",
34089
34187
  code: "catalog.updated",
34090
- message: `${at(index, "updated")} is not a date the store can parse, so it renders the raw string instead of "2 days ago".`,
34091
- path: at(index, "updated")
34188
+ message: `${pathOf(index, "updated")} is not a date the store can parse, so it renders the raw string instead of "2 days ago".`,
34189
+ path: pathOf(index, "updated")
34092
34190
  });
34093
34191
  }
34094
34192
  if (raw["repository"] !== void 0 && !isHttpUrl(raw["repository"])) {
34095
34193
  findings.push({
34096
34194
  level: "warning",
34097
34195
  code: "catalog.repository",
34098
- message: `${at(index, "repository")} must be an http(s) URL, so the host drops it and the detail pane shows no link.`,
34099
- path: at(index, "repository")
34196
+ message: `${pathOf(index, "repository")} must be an http(s) URL, so the host drops it and the detail pane shows no link.`,
34197
+ path: pathOf(index, "repository")
34100
34198
  });
34101
34199
  }
34102
34200
  return findings;
@@ -34108,8 +34206,8 @@ function validateEntryKeys(raw, index) {
34108
34206
  findings.push({
34109
34207
  level: "warning",
34110
34208
  code: "catalog.unknown-key",
34111
- message: `${at(index, key)} is not one of the fields the host reads (${CATALOG_ENTRY_KEYS.join(", ")}), so it is ignored without a word \u2014 which is exactly what a misspelled field looks like.`,
34112
- path: at(index, key)
34209
+ message: `${pathOf(index, key)} is not one of the fields the host reads (${CATALOG_ENTRY_KEYS.join(", ")}), so it is ignored without a word \u2014 which is exactly what a misspelled field looks like.`,
34210
+ path: pathOf(index, key)
34113
34211
  });
34114
34212
  }
34115
34213
  }
@@ -34150,8 +34248,8 @@ function validateCatalog(catalog, known = KNOWN_CAPABILITIES) {
34150
34248
  findings.push({
34151
34249
  level: "warning",
34152
34250
  code: "catalog.id.duplicate",
34153
- message: `${at(index, "id")} repeats "${id}". The host keeps the first entry with an id and drops the rest.`,
34154
- path: at(index, "id")
34251
+ message: `${pathOf(index, "id")} repeats "${id}". The host keeps the first entry with an id and drops the rest.`,
34252
+ path: pathOf(index, "id")
34155
34253
  });
34156
34254
  }
34157
34255
  seen.add(id);
@@ -34167,7 +34265,7 @@ function validateCommands(sources, ts) {
34167
34265
  for (const source of sources) {
34168
34266
  const file2 = ts.createSourceFile(source.path, source.text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
34169
34267
  for (const call of registerCommandCalls(ts, file2)) {
34170
- findings.push(...judge(read(ts, file2, call)));
34268
+ findings.push(...findingsFor(readRegistration(ts, file2, call)));
34171
34269
  }
34172
34270
  }
34173
34271
  findings.push({ level: "info", code: "commands.runtime", message: RUNTIME_NOTE });
@@ -34184,11 +34282,11 @@ function registerCommandCalls(ts, file2) {
34184
34282
  visit(file2);
34185
34283
  return calls;
34186
34284
  }
34187
- function read(ts, file2, call) {
34285
+ function readRegistration(ts, file2, call) {
34188
34286
  const line = file2.getLineAndCharacterOfPosition(call.getStart(file2)).line + 1;
34189
- const at2 = `${file2.fileName}:${line}`;
34287
+ const at = `${file2.fileName}:${line}`;
34190
34288
  const literal2 = call.arguments[0];
34191
- const empty = { at: at2, callable: false, described: false, answers: false, returnsValue: false, arguments: [] };
34289
+ const empty = { at, callable: false, described: false, answers: false, returnsValue: false, arguments: [] };
34192
34290
  if (!literal2 || !ts.isObjectLiteralExpression(literal2)) {
34193
34291
  return { ...empty, unreadable: "its argument is not an object literal" };
34194
34292
  }
@@ -34210,7 +34308,7 @@ function read(ts, file2, call) {
34210
34308
  const callable = properties.get("callable");
34211
34309
  const consent = properties.get("agentConsent");
34212
34310
  return {
34213
- at: at2,
34311
+ at,
34214
34312
  id: id.text,
34215
34313
  callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
34216
34314
  described: properties.has("description"),
@@ -34230,25 +34328,27 @@ function unwrapped(ts, value) {
34230
34328
  }
34231
34329
  return ts.isAsExpression(value) || ts.isSatisfiesExpression(value) || ts.isParenthesizedExpression(value) ? unwrapped(ts, value.expression) : value;
34232
34330
  }
34233
- var STATED = ["allow", "ask", "ask-always", "never"];
34331
+ var CONSENT_LINES = /* @__PURE__ */ new Map([
34332
+ ["allow", "says an agent's word is enough to run it."],
34333
+ [
34334
+ "ask",
34335
+ "says the person is asked first; whoever runs it on an agent's behalf does the asking."
34336
+ ],
34337
+ [
34338
+ "ask-always",
34339
+ "says the person is asked every time; whoever runs it on an agent's behalf does the asking."
34340
+ ],
34341
+ [
34342
+ "never",
34343
+ "says it is not to be run on an agent's word; closing it to other callers is what enforces that."
34344
+ ]
34345
+ ]);
34346
+ var STATED_CONSENTS = [...CONSENT_LINES.keys()];
34234
34347
  function consentLine(consent) {
34235
- switch (consent) {
34236
- case "allow": {
34237
- return "says an agent's word is enough to run it.";
34238
- }
34239
- case "ask": {
34240
- return "says the person is asked first; whoever runs it on an agent's behalf does the asking.";
34241
- }
34242
- case "ask-always": {
34243
- return "says the person is asked every time; whoever runs it on an agent's behalf does the asking.";
34244
- }
34245
- case "never": {
34246
- return "says it is not to be run on an agent's word; closing it to other callers is what enforces that.";
34247
- }
34248
- default: {
34249
- return consent === void 0 ? "says nothing about whether an agent's word is enough to run it." : `declares "${consent}" about an agent's word, which is none of ${STATED.join(", ")}, so nothing reads it.`;
34250
- }
34348
+ if (consent === void 0) {
34349
+ return "says nothing about whether an agent's word is enough to run it.";
34251
34350
  }
34351
+ return CONSENT_LINES.get(consent) ?? `declares "${consent}" about an agent's word, which is none of ${STATED_CONSENTS.join(", ")}, so nothing reads it.`;
34252
34352
  }
34253
34353
  function readArguments(ts, value) {
34254
34354
  if (!value || !ts.isArrayLiteralExpression(value)) {
@@ -34295,15 +34395,15 @@ function returnsValue(ts, run) {
34295
34395
  visit(run);
34296
34396
  return found;
34297
34397
  }
34298
- function judge(registration) {
34299
- const { at: at2, id } = registration;
34398
+ function findingsFor(registration) {
34399
+ const { at, id } = registration;
34300
34400
  if (registration.unreadable) {
34301
34401
  return [
34302
34402
  {
34303
34403
  level: "info",
34304
34404
  code: "command.unreadable",
34305
- path: at2,
34306
- message: `A registerCommand call at ${at2} could not be read: ${registration.unreadable}. What it offers an agent is unknown here.`
34405
+ path: at,
34406
+ message: `A registerCommand call at ${at} could not be read: ${registration.unreadable}. What it offers an agent is unknown here.`
34307
34407
  }
34308
34408
  ];
34309
34409
  }
@@ -34312,7 +34412,7 @@ function judge(registration) {
34312
34412
  {
34313
34413
  level: "info",
34314
34414
  code: "command.private",
34315
- path: at2,
34415
+ path: at,
34316
34416
  message: `${id}: not offered to an agent, because it is not callable; an agent never sees it.`
34317
34417
  }
34318
34418
  ];
@@ -34322,7 +34422,7 @@ function judge(registration) {
34322
34422
  findings.push({
34323
34423
  level: "warning",
34324
34424
  code: "command.description",
34325
- path: at2,
34425
+ path: at,
34326
34426
  message: `${id}: offered to an agent without a description; the agent sees only the id and has to guess what it does.`
34327
34427
  });
34328
34428
  }
@@ -34331,7 +34431,7 @@ function judge(registration) {
34331
34431
  findings.push({
34332
34432
  level: "info",
34333
34433
  code: "command.argument",
34334
- path: at2,
34434
+ path: at,
34335
34435
  message: `${id}: argument "${argument.name}" has no description; an agent sees a bare name and guesses its value.`
34336
34436
  });
34337
34437
  }
@@ -34340,7 +34440,7 @@ function judge(registration) {
34340
34440
  findings.push({
34341
34441
  level: "info",
34342
34442
  code: "command.answers",
34343
- path: at2,
34443
+ path: at,
34344
34444
  message: `${id}: run returns a value but declares no answers; an agent hears that the command ran, not what it answered.`
34345
34445
  });
34346
34446
  }
@@ -34349,14 +34449,14 @@ function judge(registration) {
34349
34449
  findings.push({
34350
34450
  level: "info",
34351
34451
  code: "command.offered",
34352
- path: at2,
34452
+ path: at,
34353
34453
  message: `${id}: offered to an agent, with ${count} described argument${count === 1 ? "" : "s"}${registration.answers ? " and a declared answer" : ""}.`
34354
34454
  });
34355
34455
  }
34356
34456
  findings.push({
34357
34457
  level: "info",
34358
34458
  code: "command.consent",
34359
- path: at2,
34459
+ path: at,
34360
34460
  message: `${id}: ${consentLine(registration.agentConsent)}`
34361
34461
  });
34362
34462
  return findings;
@@ -34394,7 +34494,7 @@ function listGenerators() {
34394
34494
  }))
34395
34495
  });
34396
34496
  }
34397
- function valuesFor(scaffold2, args) {
34497
+ function portableValuesFrom(scaffold2, args) {
34398
34498
  const values = {};
34399
34499
  for (const option of portableOptions(scaffold2)) {
34400
34500
  const value = args[option.name];
@@ -34409,31 +34509,31 @@ function valuesFor(scaffold2, args) {
34409
34509
  }
34410
34510
  var WHERE_THE_FILES_LAND = "<the directory you wrote these files into>";
34411
34511
  function scaffold(scaffold2, args) {
34412
- const values = valuesFor(scaffold2, args);
34512
+ const values = portableValuesFrom(scaffold2, args);
34413
34513
  const remaining = (scaffold2.amend?.({ ...values, directory: WHERE_THE_FILES_LAND }) ?? []).map((amendment) => describeAmendment(amendment));
34414
34514
  return ok({
34415
34515
  files: scaffold2.build(values),
34416
34516
  ...remaining.length > 0 && { remaining }
34417
34517
  });
34418
34518
  }
34419
- function validateManifestTool(a) {
34519
+ function validateManifestTool(args) {
34420
34520
  return ok({
34421
34521
  findings: validateManifest({
34422
- id: a["id"],
34423
- name: a["name"],
34424
- capabilities: a["capabilities"]
34522
+ id: args["id"],
34523
+ name: args["name"],
34524
+ capabilities: args["capabilities"]
34425
34525
  })
34426
34526
  });
34427
34527
  }
34428
- function validateCatalogTool(a) {
34429
- return ok({ findings: validateCatalog(a["catalog"]) });
34528
+ function validateCatalogTool(args) {
34529
+ return ok({ findings: validateCatalog(args["catalog"]) });
34430
34530
  }
34431
- function validateI18nTool(a) {
34432
- const bundles = a["bundles"] ?? {};
34531
+ function validateI18nTool(args) {
34532
+ const bundles = args["bundles"] ?? {};
34433
34533
  return ok({ findings: validateI18nParity(bundles) });
34434
34534
  }
34435
- function validateCommandsTool(a) {
34436
- const files = a["files"] ?? {};
34535
+ function validateCommandsTool(args) {
34536
+ const files = args["files"] ?? {};
34437
34537
  const ts = loadTypeScript(process.cwd());
34438
34538
  if (!ts) {
34439
34539
  return ok({
@@ -34470,7 +34570,7 @@ function inputSchema(descriptor) {
34470
34570
  function createMcpServer() {
34471
34571
  const server = new McpServer({
34472
34572
  name: "loomweaver-devkit",
34473
- version: "0.14.1"
34573
+ version: "0.14.2"
34474
34574
  });
34475
34575
  server.registerTool(
34476
34576
  "list_generators",