@loomweaver/mcp 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/main.mjs +1385 -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,571 @@ 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/scaffolds/scaffold-values.ts
31649
+ var NEUTRAL_PREFIX = "app";
31650
+ function stringValue(values, name) {
31651
+ const value = values[name];
31652
+ return typeof value === "string" ? value : void 0;
31653
+ }
31654
+ function booleanValue(values, name) {
31655
+ const value = values[name];
31656
+ return typeof value === "boolean" ? value : void 0;
31657
+ }
31658
+ var PLACEMENT_OPTIONS = [
31659
+ {
31660
+ name: "directory",
31661
+ type: "string",
31662
+ description: "Project root, relative to the workspace root.",
31663
+ workspaceOnly: true
31664
+ },
31665
+ {
31666
+ name: "tags",
31667
+ type: "string",
31668
+ description: "Comma-separated Nx tags for the project.",
31669
+ workspaceOnly: true
31670
+ },
31671
+ {
31672
+ name: "unitTestRunner",
31673
+ type: "string",
31674
+ description: "Test wiring to emit. 'vitest' uses @nx/angular:unit-test; 'none' emits no test target.",
31675
+ choices: ["vitest", "none"],
31676
+ default: "vitest",
31677
+ workspaceOnly: true
31678
+ }
31679
+ ];
31680
+ var APP_OPTION = {
31681
+ name: "app",
31682
+ type: "string",
31683
+ description: "Application to drop into. Inferred when the workspace has exactly one.",
31684
+ workspaceOnly: true
31685
+ };
31686
+
31687
+ // ../devkit/src/lib/validate/manifest.ts
31688
+ var KNOWN_CAPABILITIES = [
31689
+ "contributions",
31690
+ "ui",
31691
+ "host",
31692
+ "navigation",
31693
+ "session",
31694
+ "theme",
31695
+ "automation"
31696
+ ];
31697
+ function validateManifest(manifest2, known = KNOWN_CAPABILITIES) {
31698
+ const findings = [];
31699
+ if (typeof manifest2.id !== "string" || !isKebabId(manifest2.id)) {
31700
+ findings.push({
31701
+ level: "error",
31702
+ code: "manifest.id",
31703
+ message: `Plugin id must be a kebab-case string; got ${JSON.stringify(manifest2.id)}.`,
31704
+ path: "manifest.id"
31705
+ });
31706
+ }
31707
+ if (manifest2.name !== void 0 && typeof manifest2.name !== "string") {
31708
+ findings.push({
31709
+ level: "error",
31710
+ code: "manifest.name",
31711
+ message: "Plugin name must be a string when provided.",
31712
+ path: "manifest.name"
31713
+ });
31714
+ }
31715
+ findings.push(...validateCapabilities(manifest2.capabilities, known));
31716
+ return findings;
31717
+ }
31718
+ function validateCapabilities(capabilities, known) {
31719
+ if (capabilities === void 0) {
31720
+ return [];
31721
+ }
31722
+ if (!Array.isArray(capabilities)) {
31723
+ return [
31724
+ {
31725
+ level: "error",
31726
+ code: "manifest.capabilities",
31727
+ message: "capabilities must be an array when provided.",
31728
+ path: "manifest.capabilities"
31729
+ }
31730
+ ];
31731
+ }
31732
+ const findings = [];
31733
+ const seen = /* @__PURE__ */ new Set();
31734
+ for (const capability of capabilities) {
31735
+ if (typeof capability !== "string" || !known.includes(capability)) {
31736
+ findings.push({
31737
+ level: "error",
31738
+ code: "manifest.capability.unknown",
31739
+ message: `Unknown capability ${JSON.stringify(capability)}. Known: ${known.join(", ")}.`,
31740
+ path: "manifest.capabilities"
31741
+ });
31742
+ continue;
31743
+ }
31744
+ if (seen.has(capability)) {
31745
+ findings.push({
31746
+ level: "warning",
31747
+ code: "manifest.capability.duplicate",
31748
+ message: `Duplicate capability "${capability}".`,
31749
+ path: "manifest.capabilities"
31750
+ });
31751
+ }
31752
+ seen.add(capability);
31753
+ }
31754
+ return findings;
31755
+ }
31756
+
31757
+ // ../devkit/src/recipes/angular-weaver/weaver-input.ts
31758
+ var DEFAULT_MENU_SLOT = "content/tab/context";
31759
+ function accessLiteral(spec) {
31760
+ if (spec === "authenticated") {
31761
+ return "{ authenticated: true }";
31762
+ }
31763
+ if (spec === "anonymous") {
31764
+ return "{ authenticated: false }";
31765
+ }
31766
+ if (spec.startsWith("role:")) {
31767
+ const role = spec.slice("role:".length);
31768
+ if (!role) {
31769
+ throw new Error('Access "role:" needs a role name, e.g. "role:admin".');
31770
+ }
31771
+ if (/['\\]/.test(role)) {
31772
+ throw new Error(
31773
+ `Access role "${role}" must not contain quotes or backslashes.`
31774
+ );
31775
+ }
31776
+ return `{ anyRole: ['${role}'] }`;
31777
+ }
31778
+ throw new Error(
31779
+ `Unknown access "${spec}". Use "authenticated", "anonymous" or "role:<name>".`
31780
+ );
31781
+ }
31782
+ function resolveMenuSlot(menu) {
31783
+ if (menu === true) {
31784
+ return DEFAULT_MENU_SLOT;
31785
+ }
31786
+ return typeof menu === "string" && menu.length ? menu : void 0;
31787
+ }
31788
+ var PLATFORM_BOUND_CHORD_TOKENS = /* @__PURE__ */ new Set([
31789
+ "cmd",
31790
+ "command",
31791
+ "ctrl",
31792
+ "control",
31793
+ "meta"
31794
+ ]);
31795
+ function assertPlatformNeutralChord(shortcut) {
31796
+ const tokens = shortcut.toLowerCase().split("+").map((token) => token.trim());
31797
+ const bound = tokens.find((token) => PLATFORM_BOUND_CHORD_TOKENS.has(token));
31798
+ if (bound) {
31799
+ throw new Error(
31800
+ `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.`
31801
+ );
31802
+ }
31803
+ }
31804
+ function resolveFeatures(id, input) {
31805
+ const menuSlot = resolveMenuSlot(input?.menu);
31806
+ const barItem = Boolean(input?.barItem);
31807
+ const hasShortcut = Boolean(input?.shortcut?.trim());
31808
+ if (hasShortcut) {
31809
+ assertPlatformNeutralChord(input?.shortcut?.trim() ?? "");
31810
+ }
31811
+ const instanceable = Boolean(input?.instanceable);
31812
+ const container = Boolean(input?.container);
31813
+ if (instanceable && container) {
31814
+ throw new Error(
31815
+ '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.'
31816
+ );
31817
+ }
31818
+ const agent = Boolean(input?.agent);
31819
+ return {
31820
+ command: Boolean(input?.command) || menuSlot !== void 0 || barItem || hasShortcut || agent,
31821
+ menuSlot,
31822
+ settings: Boolean(input?.settings),
31823
+ access: input?.access ? accessLiteral(input.access) : void 0,
31824
+ shortcut: input?.shortcut?.trim() || `mod+shift+${id.charAt(0)}`,
31825
+ barItem,
31826
+ about: Boolean(input?.about),
31827
+ instanceable,
31828
+ container,
31829
+ agent,
31830
+ spec: input?.spec !== false
31831
+ };
31832
+ }
31833
+ function deriveCapabilities(features) {
31834
+ const set2 = /* @__PURE__ */ new Set(["contributions", "navigation"]);
31835
+ if (features.command) set2.add("ui");
31836
+ if (features.about) {
31837
+ set2.add("ui");
31838
+ set2.add("host");
31839
+ }
31840
+ if (features.agent) {
31841
+ set2.add("ui");
31842
+ set2.add("automation");
31843
+ }
31844
+ return KNOWN_CAPABILITIES.filter((capability) => set2.has(capability));
31845
+ }
31846
+ function resolvePrefix(prefix) {
31847
+ const trimmed = prefix?.trim();
31848
+ if (!trimmed) {
31849
+ return NEUTRAL_PREFIX;
31850
+ }
31851
+ if (!isKebabId(trimmed)) {
31852
+ throw new Error(
31853
+ `Selector prefix must be kebab-case (e.g. "acme"); got "${trimmed}".`
31854
+ );
31855
+ }
31856
+ return trimmed;
31857
+ }
31858
+ function resolveWeaverInput(input) {
31859
+ if (!isKebabId(input.id)) {
31860
+ throw new Error(
31861
+ `Weaver id must be kebab-case (e.g. "notes"); got "${input.id}".`
31862
+ );
31863
+ }
31864
+ const features = resolveFeatures(input.id, input.features);
31865
+ const capabilities = input.capabilities?.length ? [...input.capabilities] : deriveCapabilities(features);
31866
+ return {
31867
+ id: input.id,
31868
+ name: input.name?.trim() || toTitleCase(input.id),
31869
+ className: toPascalCase(input.id),
31870
+ propertyName: toCamelCase(input.id),
31871
+ capabilities,
31872
+ features,
31873
+ prefix: resolvePrefix(input.prefix),
31874
+ importPath: input.importPath?.trim() || `@loomweaver/${input.id}-weaver`
31875
+ };
31876
+ }
31877
+
31878
+ // ../devkit/src/recipes/angular-weaver/weaver-command.ts
31879
+ function commandBlock(weaver) {
31880
+ return [
31881
+ " ctx.registerCommand({",
31882
+ ` id: '${weaver.id}.hello',`,
31883
+ ` title: '${weaver.id}.action',`,
31884
+ ` description: '${weaver.id}.actionDescription',`,
31885
+ " arguments: [",
31886
+ ` { name: 'tone', kind: 'choice', choices: ['info', 'success', 'warning'], description: '${weaver.id}.actionTone' },`,
31887
+ " ],",
31888
+ ` answers: '${weaver.id}.actionAnswers',`,
31889
+ ` shortcut: '${weaver.features.shortcut}',`,
31890
+ " callable: true,",
31891
+ ...weaver.features.agent ? [
31892
+ " // What an agent's word is enough for. The platform states it and enforces nothing:",
31893
+ " // the asking is the connection's half, which reads this off the call.",
31894
+ " agentConsent: 'ask',"
31895
+ ] : [],
31896
+ " run: (_context, args) => {",
31897
+ " const tone = toneOf(args?.['tone']);",
31898
+ ` ctx.ui.toast({ message: '${weaver.id}.action', kind: tone });`,
31899
+ " return { tone };",
31900
+ " },",
31901
+ " });"
31902
+ ].join("\n");
31903
+ }
31904
+ function toneHelper() {
31905
+ return [
31906
+ "type Tone = 'info' | 'success' | 'warning';",
31907
+ "",
31908
+ "// The workbench already refused anything outside the declared choices; this narrows the type.",
31909
+ "function toneOf(value: unknown): Tone {",
31910
+ " return value === 'success' || value === 'warning' ? value : 'info';",
31911
+ "}"
31912
+ ].join("\n");
31913
+ }
31914
+
31915
+ // ../devkit/src/recipes/angular-weaver/weaver-plugin.ts
31625
31916
  var CONTAINER_EXAMPLE_ID = "example";
31626
- function capabilityItems(capabilities) {
31627
- return capabilities.map((capability) => `'${capability}'`).join(", ");
31917
+ 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>';
31918
+ function containerChildIds(weaver) {
31919
+ return [`${weaver.id}.canvas`, `${weaver.id}.details`];
31920
+ }
31921
+ function containerSurfaceBlock(weaver) {
31922
+ const children = containerChildIds(weaver).map((id) => `'${id}'`).join(", ");
31923
+ const lines = [
31924
+ " ctx.registerSurface({",
31925
+ ` id: '${weaver.id}',`,
31926
+ ` title: '${weaver.id}.title',`,
31927
+ ` icon: '${weaver.id}',`,
31928
+ ` routable: { path: '${weaver.id}/:id' },`,
31929
+ " container: {",
31930
+ ` children: [${children}],`,
31931
+ ` initial: [${children}],`,
31932
+ " },",
31933
+ ...accessLine(weaver),
31934
+ " });"
31935
+ ];
31936
+ for (const [suffix, className] of [
31937
+ ["canvas", `${weaver.className}CanvasView`],
31938
+ ["details", `${weaver.className}DetailsView`]
31939
+ ]) {
31940
+ lines.push(
31941
+ " ctx.registerSurface({",
31942
+ ` id: '${weaver.id}.${suffix}',`,
31943
+ ` title: '${weaver.id}.${suffix}',`,
31944
+ " docks: [],",
31945
+ ` component: ${className},`,
31946
+ " });"
31947
+ );
31948
+ }
31949
+ return lines.join("\n");
31950
+ }
31951
+ function surfaceBlock(weaver) {
31952
+ if (weaver.features.container) {
31953
+ return containerSurfaceBlock(weaver);
31954
+ }
31955
+ const lines = [
31956
+ " ctx.registerSurface({",
31957
+ ` id: '${weaver.id}',`,
31958
+ ` title: '${weaver.id}.title',`,
31959
+ ` icon: '${weaver.id}',`,
31960
+ ` component: ${weaver.className}View,`
31961
+ ];
31962
+ if (weaver.features.instanceable) {
31963
+ lines.push(` docks: ['${LEFT_PANEL_REGION}'],`, " instanceable: true,");
31964
+ } else {
31965
+ lines.push(` routable: { path: '${weaver.id}' },`);
31966
+ }
31967
+ lines.push(...accessLine(weaver), " });");
31968
+ return lines.join("\n");
31969
+ }
31970
+ function railTarget(weaver) {
31971
+ if (weaver.features.container) {
31972
+ return `ctx.navigateContent('${weaver.id}/${CONTAINER_EXAMPLE_ID}')`;
31973
+ }
31974
+ if (weaver.features.instanceable) {
31975
+ return `ctx.revealSurface('${weaver.id}')`;
31976
+ }
31977
+ return `ctx.navigateContent('${weaver.id}')`;
31978
+ }
31979
+ function railBlock(weaver) {
31980
+ const lines = [
31981
+ " ctx.registerRailItem({",
31982
+ ` id: '${weaver.id}.rail',`,
31983
+ ` rail: '${RAIL_REGION}',`,
31984
+ ` icon: '${weaver.id}',`,
31985
+ ` title: '${weaver.id}.title',`,
31986
+ ` run: () => ${railTarget(weaver)},`,
31987
+ ...accessLine(weaver),
31988
+ " });"
31989
+ ];
31990
+ return lines.join("\n");
31991
+ }
31992
+ function barItemBlock(weaver) {
31993
+ return [
31994
+ " ctx.registerBarItem({",
31995
+ ` id: '${weaver.id}.bar',`,
31996
+ ` bar: '${STATUS_BAR_REGION}',`,
31997
+ " slot: 'end',",
31998
+ ` icon: '${weaver.id}',`,
31999
+ ` tooltip: '${weaver.id}.action',`,
32000
+ ` command: '${weaver.id}.hello',`,
32001
+ " });"
32002
+ ].join("\n");
32003
+ }
32004
+ function aboutCommandBlock(weaver) {
32005
+ return [
32006
+ " ctx.registerCommand({",
32007
+ ` id: '${weaver.id}.about',`,
32008
+ ` title: '${weaver.id}.about',`,
32009
+ ` run: () => ctx.ui.open(${weaver.className}AboutDialog, { data: ctx.host, title: '${weaver.id}.title' }),`,
32010
+ " });"
32011
+ ].join("\n");
32012
+ }
32013
+ function aboutRailBlock(weaver) {
32014
+ return [
32015
+ " ctx.registerRailItem({",
32016
+ ` id: '${weaver.id}.rail.about',`,
32017
+ ` rail: '${RAIL_REGION}',`,
32018
+ " anchor: 'bottom',",
32019
+ ` icon: '${weaver.id}',`,
32020
+ ` title: '${weaver.id}.about',`,
32021
+ ` command: '${weaver.id}.about',`,
32022
+ " });"
32023
+ ].join("\n");
32024
+ }
32025
+ function menuBlock(weaver) {
32026
+ return [
32027
+ " ctx.registerMenuItem({",
32028
+ ` menu: '${weaver.features.menuSlot}',`,
32029
+ ` command: '${weaver.id}.hello',`,
32030
+ " });"
32031
+ ].join("\n");
32032
+ }
32033
+ function settingsBlock(weaver) {
32034
+ return [
32035
+ " ctx.registerSettingsSection({",
32036
+ ` id: '${weaver.id}',`,
32037
+ ` title: '${weaver.id}.settings.title',`,
32038
+ " rows: [",
32039
+ " {",
32040
+ " id: 'enabled',",
32041
+ ` label: '${weaver.id}.settings.enabled',`,
32042
+ ` control: { kind: 'toggle', value: () => ${weaver.propertyName}Enabled(), set: (v) => ${weaver.propertyName}Enabled.set(v) },`,
32043
+ " },",
32044
+ " {",
32045
+ " id: 'note',",
32046
+ ` label: '${weaver.id}.settings.note',`,
32047
+ ` control: { kind: 'text', value: () => ${weaver.propertyName}Note(), set: (v) => ${weaver.propertyName}Note.set(v) },`,
32048
+ " },",
32049
+ " ],",
32050
+ " });"
32051
+ ].join("\n");
32052
+ }
32053
+ function pluginImports(weaver) {
32054
+ const imports = ["import { Plugin } from '@loomweaver/plugin-sdk';"];
32055
+ if (weaver.features.container) {
32056
+ imports.push(
32057
+ `import { ${weaver.className}CanvasView } from '../views/${weaver.id}-canvas-view';`,
32058
+ `import { ${weaver.className}DetailsView } from '../views/${weaver.id}-details-view';`
32059
+ );
32060
+ } else {
32061
+ imports.push(`import { ${weaver.className}View } from '../views/${weaver.id}-view';`);
32062
+ }
32063
+ if (weaver.features.about) {
32064
+ imports.push(
32065
+ `import { ${weaver.className}AboutDialog } from '../dialogs/${weaver.id}-about-dialog';`
32066
+ );
32067
+ }
32068
+ if (weaver.features.agent) {
32069
+ imports.push(
32070
+ `import { ${weaver.propertyName}Agent, ${weaver.propertyName}Connection } from '../agent/${weaver.id}-agent';`,
32071
+ `import { ${weaver.className}AgentPanel } from '../agent/${weaver.id}-agent-panel';`
32072
+ );
32073
+ }
32074
+ if (weaver.features.settings) {
32075
+ imports.unshift("import { signal } from '@angular/core';");
32076
+ }
32077
+ return imports;
32078
+ }
32079
+ function pluginConsts(weaver) {
32080
+ const consts = [`const icon =
32081
+ '${SURFACE_ICON}';`];
32082
+ if (weaver.features.settings) {
32083
+ consts.push(
32084
+ `const ${weaver.propertyName}Enabled = signal(true);`,
32085
+ `const ${weaver.propertyName}Note = signal('');`
32086
+ );
32087
+ }
32088
+ if (weaver.features.command) consts.push(toneHelper());
32089
+ return consts;
32090
+ }
32091
+ function pluginBody(weaver) {
32092
+ const body = [` ctx.contributeIcons({ '${weaver.id}': icon });`];
32093
+ if (weaver.features.agent) {
32094
+ body.push(
32095
+ ` ${weaver.propertyName}Agent.set(${weaver.propertyName}Connection(ctx));`
32096
+ );
32097
+ }
32098
+ if (weaver.features.command) body.push(commandBlock(weaver));
32099
+ if (weaver.features.about) body.push(aboutCommandBlock(weaver));
32100
+ body.push(surfaceBlock(weaver), railBlock(weaver));
32101
+ if (weaver.features.about) body.push(aboutRailBlock(weaver));
32102
+ if (weaver.features.barItem) body.push(barItemBlock(weaver));
32103
+ if (weaver.features.menuSlot) body.push(menuBlock(weaver));
32104
+ if (weaver.features.settings) body.push(settingsBlock(weaver));
32105
+ if (weaver.features.agent) body.push(agentSurfaceBlock(weaver));
32106
+ return body;
32107
+ }
32108
+ function pluginFile(weaver) {
32109
+ const body = pluginBody(weaver);
32110
+ const deactivate = weaver.features.agent ? `
32111
+ deactivate() {
32112
+ ${weaver.propertyName}Agent.set(null);
32113
+ },` : "";
32114
+ return `${pluginImports(weaver).join("\n")}
32115
+
32116
+ ${pluginConsts(weaver).join("\n\n")}
32117
+
32118
+ export const ${weaver.propertyName}Plugin: Plugin = {
32119
+ manifest: {
32120
+ id: '${weaver.id}',
32121
+ name: '${weaver.name}',
32122
+ capabilities: [${quotedList(weaver.capabilities)}],
32123
+ },
32124
+ activate(ctx) {
32125
+ ${body.join("\n")}
32126
+ },${deactivate}
32127
+ };
32128
+ `;
32129
+ }
32130
+ function accessLine(weaver) {
32131
+ return weaver.features.access ? [` access: ${weaver.features.access},`] : [];
32132
+ }
32133
+
32134
+ // ../devkit/src/recipes/platform-version.ts
32135
+ var PLATFORM_VERSION = "0.14.3";
32136
+
32137
+ // ../devkit/src/recipes/readme-notes.ts
32138
+ var UNTAGGED_NOTE = [
32139
+ "The project is generated **untagged**: Nx tags belong to your `depConstraints`, and inventing",
32140
+ "one would fail a lint policy you never opted this project into. If your workspace enforces",
32141
+ "module boundaries, give it tags your constraints allow \u2014 `--tags` at generation time, or",
32142
+ "`tags` in `project.json` afterwards."
32143
+ ];
32144
+ function asBullet(lines) {
32145
+ return lines.map((line, index) => index === 0 ? `- ${line}` : ` ${line}`);
31628
32146
  }
31629
32147
 
31630
32148
  // ../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) {
32149
+ function surfaceNotes(weaver) {
32150
+ const railNote = "Rail and bar items reference region ids (`primary` rail, `status-bar` bar) that must exist in your layout.";
32151
+ if (weaver.features.container) {
31634
32152
  return [
31635
- `The surface is a **container**: it is routable at \`/${w.id}/:id\`, and its tab holds a`,
32153
+ `The surface is a **container**: it is routable at \`/${weaver.id}/:id\`, and its tab holds a`,
31636
32154
  "nested pane tree of child surfaces. The host draws the inner tabs, splits and drag targets; this",
31637
32155
  "weaver only declares which children it offers.",
31638
32156
  "",
@@ -31652,9 +32170,9 @@ function surfaceNotes(w) {
31652
32170
  railNote
31653
32171
  ];
31654
32172
  }
31655
- if (w.features.instanceable) {
32173
+ if (weaver.features.instanceable) {
31656
32174
  return [
31657
- `The surface is **docked** into the \`primary\` region and marked \`instanceable\`, so the host shows a`,
32175
+ `The surface is **docked** into the \`left-panel\` region and marked \`instanceable\`, so the host shows a`,
31658
32176
  "switcher for saving, naming, renaming and deleting several configurations of it, each with its own",
31659
32177
  "`VIEW_STATE` blob.",
31660
32178
  "",
@@ -31672,7 +32190,7 @@ function surfaceNotes(w) {
31672
32190
  ];
31673
32191
  }
31674
32192
  return [
31675
- `The surface is routable at \`/${w.id}\`; ${railNote.charAt(0).toLowerCase()}${railNote.slice(1)}`,
32193
+ `The surface is routable at \`/${weaver.id}\`; ${railNote.charAt(0).toLowerCase()}${railNote.slice(1)}`,
31676
32194
  "",
31677
32195
  "A routable surface has **no `VIEW_STATE` handle** \u2014 injecting the token there throws. It owns a URL,",
31678
32196
  "so anything shareable (a filter, the active sub-tab) belongs in route params or `subRoutes`, where it",
@@ -31681,18 +32199,18 @@ function surfaceNotes(w) {
31681
32199
  "flavour instead."
31682
32200
  ];
31683
32201
  }
31684
- function agentNotes(w) {
32202
+ function agentNotes(weaver) {
31685
32203
  return [
31686
32204
  "",
31687
32205
  "## The agent connection",
31688
32206
  "",
31689
32207
  `\`src/lib/agent/\` holds three files and one of them is meant to be thrown away.`,
31690
32208
  "",
31691
- `- \`${w.id}-agent.ts\` is the connection: the workbench's own commands offered as tools, and a`,
32209
+ `- \`${weaver.id}-agent.ts\` is the connection: the workbench's own commands offered as tools, and a`,
31692
32210
  " seam where this weaver decides about a call before it runs. Nothing is registered twice \u2014 the",
31693
32211
  " 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`,
32212
+ `- \`${weaver.id}-agent-panel.ts\` shows what is offered, the call as it streams, and the outcome.`,
32213
+ `- \`${weaver.id}-agent-source.ts\` is a **stand-in**, not an assistant: it produces the protocol's own`,
31696
32214
  " events so the whole path runs before you have connected anything. Replace that one file with",
31697
32215
  " your transport and nothing else changes. No transport, credential or model is generated for",
31698
32216
  " you, because none of them can be guessed.",
@@ -31705,15 +32223,15 @@ function agentNotes(w) {
31705
32223
  `The generated weaver needs two packages your project may not carry yet:`,
31706
32224
  "",
31707
32225
  "```bash",
31708
- `npm i @loomweaver/ag-ui @ag-ui/core`,
32226
+ `npm i @loomweaver/ag-ui@^${PLATFORM_VERSION} @ag-ui/core@${AG_UI_PROTOCOL_VERSION}`,
31709
32227
  "```",
31710
32228
  "",
31711
32229
  "The Nx generator and the CLI record them for you; the MCP route names them instead."
31712
32230
  ];
31713
32231
  }
31714
- function readmeFile(w) {
32232
+ function readmeFile(weaver) {
31715
32233
  return [
31716
- `# ${w.name} weaver`,
32234
+ `# ${weaver.name} weaver`,
31717
32235
  "",
31718
32236
  `A LoomWeaver weaver (a domain plugin bundle). It consumes only the public \`@loomweaver/plugin-sdk\` contract.`,
31719
32237
  "",
@@ -31723,22 +32241,22 @@ function readmeFile(w) {
31723
32241
  ` returns an array, so spread it:`,
31724
32242
  "",
31725
32243
  " ```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),`,
32244
+ ` import { ${weaver.propertyName}Plugin } from '${weaver.importPath}'; // Nx: the workspace alias; without one, a relative path to this library's src/index.ts`,
32245
+ ` ...providePlugins(${weaver.propertyName}Plugin),`,
31728
32246
  " ```",
31729
32247
  "",
31730
32248
  `2. Grant its capabilities (default-deny) via \`provideCapabilityGrants\`:`,
31731
32249
  "",
31732
32250
  " ```ts",
31733
- ` provideCapabilityGrants({ '${w.id}': [${capabilityItems(w.capabilities)}] });`,
32251
+ ` provideCapabilityGrants({ '${weaver.id}': [${quotedList(weaver.capabilities)}] });`,
31734
32252
  " ```",
31735
32253
  "",
31736
- `3. Compose its translations with \`provideTranslationNamespaces('${w.id}')\` \u2014 and serve the`,
32254
+ `3. Compose its translations with \`provideTranslationNamespaces('${weaver.id}')\` \u2014 and serve the`,
31737
32255
  ` 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):`,
32256
+ ` \`/i18n/${weaver.id}/<lang>.json\` (the Nx generator adds this glob for you):`,
31739
32257
  "",
31740
32258
  " ```json",
31741
- ` { "glob": "**/*.json", "input": "<path to this library>/src/lib/i18n", "output": "i18n/${w.id}" }`,
32259
+ ` { "glob": "**/*.json", "input": "<path to this library>/src/lib/i18n", "output": "i18n/${weaver.id}" }`,
31742
32260
  " ```",
31743
32261
  "",
31744
32262
  `4. If your application compiles the shell's theme with Tailwind, name this library as a source`,
@@ -31751,459 +32269,65 @@ function readmeFile(w) {
31751
32269
  ` @source '<path from that stylesheet to this library>/src';`,
31752
32270
  " ```",
31753
32271
  "",
31754
- ...surfaceNotes(w),
31755
- ...w.features.agent ? agentNotes(w) : [],
32272
+ ...surfaceNotes(weaver),
32273
+ ...weaver.features.agent ? agentNotes(weaver) : [],
31756
32274
  "",
31757
32275
  "## After scaffolding",
31758
32276
  "",
31759
32277
  "- `src/lib/i18n/de.json` starts as a copy of the English strings \u2014 translate it.",
31760
32278
  `- 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.",
32279
+ ...asBullet(UNTAGGED_NOTE),
31765
32280
  ""
31766
32281
  ].join("\n");
31767
32282
  }
31768
32283
 
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) {
32284
+ // ../devkit/src/recipes/angular-weaver/weaver-views.ts
32285
+ function aboutDialogFile(weaver) {
32158
32286
  return `import { Component, inject } from '@angular/core';
32159
32287
  import { DialogRef, PluginHost } from '@loomweaver/plugin-sdk';
32160
32288
 
32161
32289
  @Component({
32162
- selector: '${w.prefix}-${w.id}-about-dialog',
32163
- templateUrl: './${w.id}-about-dialog.html',
32290
+ selector: '${weaver.prefix}-${weaver.id}-about-dialog',
32291
+ templateUrl: './${weaver.id}-about-dialog.html',
32164
32292
  })
32165
- export class ${w.className}AboutDialog {
32293
+ export class ${weaver.className}AboutDialog {
32166
32294
  protected readonly host = inject(DialogRef).data as PluginHost;
32167
32295
  }
32168
32296
  `;
32169
32297
  }
32170
- function aboutDialogTemplateFile(w) {
32298
+ function aboutDialogTemplateFile(weaver) {
32171
32299
  return `<div class="flex flex-col items-center gap-2 text-center">
32172
- <h2 class="text-lg font-semibold text-content">${w.name}</h2>
32300
+ <h2 class="text-lg font-semibold text-content">${weaver.name}</h2>
32173
32301
  <span class="text-xs text-content-faint tabular-nums">v{{ host.version() }}</span>
32174
32302
  </div>
32175
32303
  `;
32176
32304
  }
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) {
32305
+ function viewFile(weaver) {
32306
+ if (!weaver.features.instanceable) {
32183
32307
  return `import { Component } from '@angular/core';
32184
32308
 
32185
32309
  @Component({
32186
- selector: '${w.prefix}-${w.id}-view',
32187
- templateUrl: './${w.id}-view.html',
32310
+ selector: '${weaver.prefix}-${weaver.id}-view',
32311
+ templateUrl: './${weaver.id}-view.html',
32188
32312
  })
32189
- export class ${w.className}View {}
32313
+ export class ${weaver.className}View {}
32190
32314
  `;
32191
32315
  }
32192
32316
  return `import { Component, computed, inject } from '@angular/core';
32193
32317
  import { VIEW_STATE, type ViewState } from '@loomweaver/plugin-sdk';
32194
32318
 
32195
- interface ${w.className}State {
32319
+ interface ${weaver.className}State {
32196
32320
  readonly sort: 'natural' | 'alpha';
32197
32321
  }
32198
32322
 
32199
- const FRESH: ${w.className}State = { sort: 'natural' };
32323
+ const FRESH: ${weaver.className}State = { sort: 'natural' };
32200
32324
 
32201
32325
  @Component({
32202
- selector: '${w.prefix}-${w.id}-view',
32203
- templateUrl: './${w.id}-view.html',
32326
+ selector: '${weaver.prefix}-${weaver.id}-view',
32327
+ templateUrl: './${weaver.id}-view.html',
32204
32328
  })
32205
- export class ${w.className}View {
32206
- private readonly viewState = inject(VIEW_STATE) as ViewState<${w.className}State>;
32329
+ export class ${weaver.className}View {
32330
+ private readonly viewState = inject(VIEW_STATE) as ViewState<${weaver.className}State>;
32207
32331
 
32208
32332
  // undefined = a fresh instance, so apply your own default.
32209
32333
  private readonly state = computed(() => this.viewState.value() ?? FRESH);
@@ -32220,8 +32344,8 @@ export class ${w.className}View {
32220
32344
  }
32221
32345
  `;
32222
32346
  }
32223
- function viewTemplateFile(w) {
32224
- const stateNote = w.features.instanceable ? ` <button type="button" class="lw-btn lw-btn--default self-start" (click)="toggleSort()">
32347
+ function viewTemplateFile(weaver) {
32348
+ const stateNote = weaver.features.instanceable ? ` <button type="button" class="lw-btn lw-btn--default self-start" (click)="toggleSort()">
32225
32349
  Sort: {{ sort() }}
32226
32350
  </button>
32227
32351
  <p class="text-sm text-content-faint">
@@ -32230,7 +32354,7 @@ function viewTemplateFile(w) {
32230
32354
  </p>
32231
32355
  ` : "";
32232
32356
  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>
32357
+ <h2 class="text-lg font-semibold text-content">${weaver.name}</h2>
32234
32358
  <p class="text-sm text-content-faint">
32235
32359
  Your new weaver surface. Register more surfaces, commands, rail items and menus on
32236
32360
  <code class="text-content">ctx</code> inside the plugin's
@@ -32239,13 +32363,13 @@ function viewTemplateFile(w) {
32239
32363
  ${stateNote}</div>
32240
32364
  `;
32241
32365
  }
32242
- function childViewFile(w, suffix, className) {
32366
+ function childViewFile(weaver, suffix, className) {
32243
32367
  return `import { Component, inject } from '@angular/core';
32244
32368
  import { ActivatedRoute } from '@angular/router';
32245
32369
 
32246
32370
  @Component({
32247
- selector: '${w.prefix}-${w.id}-${suffix}-view',
32248
- templateUrl: './${w.id}-${suffix}-view.html',
32371
+ selector: '${weaver.prefix}-${weaver.id}-${suffix}-view',
32372
+ templateUrl: './${weaver.id}-${suffix}-view.html',
32249
32373
  })
32250
32374
  export class ${className} {
32251
32375
  private readonly route = inject(ActivatedRoute, { optional: true });
@@ -32255,7 +32379,7 @@ export class ${className} {
32255
32379
  }
32256
32380
  `;
32257
32381
  }
32258
- function childViewTemplateFile(w, heading) {
32382
+ function childViewTemplateFile(weaver, heading) {
32259
32383
  return `<div class="flex h-full flex-col gap-3 p-4">
32260
32384
  <h3 class="text-sm font-semibold text-content">${heading}</h3>
32261
32385
  <p class="text-sm text-content-faint">
@@ -32266,60 +32390,66 @@ function childViewTemplateFile(w, heading) {
32266
32390
  </div>
32267
32391
  `;
32268
32392
  }
32269
- function specFile2(w) {
32270
- return `import { ${w.propertyName}Plugin } from './${w.id}.plugin';
32393
+ function specFile2(weaver) {
32394
+ return `import { ${weaver.propertyName}Plugin } from './${weaver.id}.plugin';
32271
32395
 
32272
- describe('${w.propertyName}Plugin', () => {
32396
+ describe('${weaver.propertyName}Plugin', () => {
32273
32397
  it('declares its manifest', () => {
32274
- expect(${w.propertyName}Plugin.manifest.id).toBe('${w.id}');
32275
- expect(${w.propertyName}Plugin.manifest.capabilities).toContain('contributions');
32398
+ expect(${weaver.propertyName}Plugin.manifest.id).toBe('${weaver.id}');
32399
+ expect(${weaver.propertyName}Plugin.manifest.capabilities).toContain('contributions');
32276
32400
  });
32277
32401
  });
32278
32402
  `;
32279
32403
  }
32404
+
32405
+ // ../devkit/src/recipes/angular-weaver/recipe.ts
32406
+ function indexFile(weaver) {
32407
+ return `export { ${weaver.propertyName}Plugin } from './lib/plugin/${weaver.id}.plugin';
32408
+ `;
32409
+ }
32280
32410
  var angularWeaver = {
32281
32411
  id: "angular-weaver",
32282
32412
  build(input) {
32283
- const w = resolveWeaverInput(input);
32413
+ const weaver = resolveWeaverInput(input);
32284
32414
  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)
32415
+ "src/index.ts": indexFile(weaver),
32416
+ [`src/lib/plugin/${weaver.id}.plugin.ts`]: pluginFile(weaver),
32417
+ "src/lib/i18n/en.json": i18nFile(weaver),
32418
+ "src/lib/i18n/de.json": i18nFile(weaver),
32419
+ "README.md": readmeFile(weaver)
32290
32420
  };
32291
- if (w.features.container) {
32292
- files[`src/lib/views/${w.id}-canvas-view.ts`] = childViewFile(
32293
- w,
32421
+ if (weaver.features.container) {
32422
+ files[`src/lib/views/${weaver.id}-canvas-view.ts`] = childViewFile(
32423
+ weaver,
32294
32424
  "canvas",
32295
- `${w.className}CanvasView`
32425
+ `${weaver.className}CanvasView`
32296
32426
  );
32297
- files[`src/lib/views/${w.id}-canvas-view.html`] = childViewTemplateFile(
32298
- w,
32427
+ files[`src/lib/views/${weaver.id}-canvas-view.html`] = childViewTemplateFile(
32428
+ weaver,
32299
32429
  "Canvas"
32300
32430
  );
32301
- files[`src/lib/views/${w.id}-details-view.ts`] = childViewFile(
32302
- w,
32431
+ files[`src/lib/views/${weaver.id}-details-view.ts`] = childViewFile(
32432
+ weaver,
32303
32433
  "details",
32304
- `${w.className}DetailsView`
32434
+ `${weaver.className}DetailsView`
32305
32435
  );
32306
- files[`src/lib/views/${w.id}-details-view.html`] = childViewTemplateFile(
32307
- w,
32436
+ files[`src/lib/views/${weaver.id}-details-view.html`] = childViewTemplateFile(
32437
+ weaver,
32308
32438
  "Details"
32309
32439
  );
32310
32440
  } else {
32311
- files[`src/lib/views/${w.id}-view.ts`] = viewFile(w);
32312
- files[`src/lib/views/${w.id}-view.html`] = viewTemplateFile(w);
32441
+ files[`src/lib/views/${weaver.id}-view.ts`] = viewFile(weaver);
32442
+ files[`src/lib/views/${weaver.id}-view.html`] = viewTemplateFile(weaver);
32313
32443
  }
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);
32444
+ if (weaver.features.about) {
32445
+ files[`src/lib/dialogs/${weaver.id}-about-dialog.ts`] = aboutDialogFile(weaver);
32446
+ files[`src/lib/dialogs/${weaver.id}-about-dialog.html`] = aboutDialogTemplateFile(weaver);
32317
32447
  }
32318
- if (w.features.agent) {
32319
- Object.assign(files, agentFiles(w));
32448
+ if (weaver.features.agent) {
32449
+ Object.assign(files, agentFiles(weaver));
32320
32450
  }
32321
- if (w.features.spec) {
32322
- files[`src/lib/plugin/${w.id}.plugin.spec.ts`] = specFile2(w);
32451
+ if (weaver.features.spec) {
32452
+ files[`src/lib/plugin/${weaver.id}.plugin.spec.ts`] = specFile2(weaver);
32323
32453
  }
32324
32454
  return files;
32325
32455
  }
@@ -32328,16 +32458,16 @@ var angularWeaver = {
32328
32458
  // ../devkit/src/recipes/frame-plugin/recipe.ts
32329
32459
  function resolveFramePluginInput(input) {
32330
32460
  if (!isKebabId(input.id)) {
32331
- throw new Error(`Sandbox plugin id must be kebab-case (e.g. "notes"); got "${input.id}".`);
32461
+ throw new Error(`Frame plugin id must be kebab-case (e.g. "notes"); got "${input.id}".`);
32332
32462
  }
32333
32463
  return { id: input.id, name: input.name?.trim() || toTitleCase(input.id) };
32334
32464
  }
32335
- function pluginHtml(p) {
32465
+ function pluginHtml(plugin) {
32336
32466
  return `<!doctype html>
32337
32467
  <html lang="en">
32338
32468
  <head>
32339
32469
  <meta charset="utf-8" />
32340
- <title>${p.name} \u2014 frame plugin (logic)</title>
32470
+ <title>${plugin.name} \u2014 frame plugin (logic)</title>
32341
32471
  </head>
32342
32472
  <body>
32343
32473
  <script src="/frame-kit/penpal.global.js"></script>
@@ -32346,7 +32476,7 @@ function pluginHtml(p) {
32346
32476
  </html>
32347
32477
  `;
32348
32478
  }
32349
- function pluginJs(p) {
32479
+ function pluginJs(plugin) {
32350
32480
  return `(function () {
32351
32481
  const Penpal = globalThis.Penpal;
32352
32482
  const messenger = new Penpal.WindowMessenger({
@@ -32358,27 +32488,27 @@ function pluginJs(p) {
32358
32488
  connection.promise
32359
32489
  .then(function (ctx) {
32360
32490
  return Promise.all([
32361
- ctx.toast({ message: '${p.name} ready', kind: 'success', timeoutMs: 4000 }),
32491
+ ctx.toast({ message: '${plugin.name} ready', kind: 'success', timeoutMs: 4000 }),
32362
32492
  ctx.registerSurface({
32363
- id: '${p.id}.view',
32364
- title: '${p.name}',
32365
- iframe: '/${p.id}/view.html',
32366
- routable: { path: '${p.id}', titleIsLiteral: true },
32493
+ id: '${plugin.id}.view',
32494
+ title: '${plugin.name}',
32495
+ iframe: '/${plugin.id}/view.html',
32496
+ routable: { path: '${plugin.id}', titleIsLiteral: true },
32367
32497
  }),
32368
32498
  ]);
32369
32499
  })
32370
32500
  .catch(function (error) {
32371
- console.error('[${p.id}] frame plugin failed', error);
32501
+ console.error('[${plugin.id}] frame plugin failed', error);
32372
32502
  });
32373
32503
  })();
32374
32504
  `;
32375
32505
  }
32376
- function viewHtml(p) {
32506
+ function viewHtml(plugin) {
32377
32507
  return `<!doctype html>
32378
32508
  <html lang="en">
32379
32509
  <head>
32380
32510
  <meta charset="utf-8" />
32381
- <title>${p.name}</title>
32511
+ <title>${plugin.name}</title>
32382
32512
  <link rel="stylesheet" href="/frame-kit/lw-frame.css" />
32383
32513
  <style>
32384
32514
  body {
@@ -32394,7 +32524,7 @@ function viewHtml(p) {
32394
32524
  </head>
32395
32525
  <body>
32396
32526
  <div class="wrap">
32397
- <h1>${p.name}</h1>
32527
+ <h1>${plugin.name}</h1>
32398
32528
  <p>
32399
32529
  Your sandboxed surface. It runs isolated in its own iframe, so its body can be built with
32400
32530
  any framework (React, Vue, Svelte, vanilla). The frame UI kit (served by the distribution
@@ -32422,16 +32552,16 @@ function viewHtml(p) {
32422
32552
  </html>
32423
32553
  `;
32424
32554
  }
32425
- function readme(p) {
32555
+ function readme(plugin) {
32426
32556
  return [
32427
- `# ${p.name} \u2014 frame plugin`,
32557
+ `# ${plugin.name} \u2014 frame plugin`,
32428
32558
  "",
32429
32559
  `A framework-agnostic LoomWeaver plugin: it runs in an isolated \`<iframe sandbox>\` and`,
32430
32560
  `receives \`ctx\` over Penpal RPC through the same default-deny broker a trusted plugin uses.`,
32431
32561
  "",
32432
32562
  "## Serve it + wire it into a distribution",
32433
32563
  "",
32434
- `1. Put these files under the distribution's static dir, e.g. \`public/${p.id}/\`. The plugin`,
32564
+ `1. Put these files under the distribution's static dir, e.g. \`public/${plugin.id}/\`. The plugin`,
32435
32565
  ` references the **frame UI kit** (\`@loomweaver/frame-kit\`) at \`/frame-kit/\` \u2014`,
32436
32566
  ` the distribution serves it via an assets glob (generated distributions already do):`,
32437
32567
  "",
@@ -32442,43 +32572,40 @@ function readme(p) {
32442
32572
  `2. Register + grant it in the composition root:`,
32443
32573
  "",
32444
32574
  " ```ts",
32445
- ` provideFramePlugins({ id: '${p.id}', entryUrl: '/${p.id}/plugin.html', capabilities: ['contributions', 'ui'] });`,
32446
- ` provideCapabilityGrants({ '${p.id}': ['contributions', 'ui'] });`,
32575
+ ` provideFramePlugins({ id: '${plugin.id}', entryUrl: '/${plugin.id}/plugin.html', capabilities: ['contributions', 'ui'] });`,
32576
+ ` provideCapabilityGrants({ '${plugin.id}': ['contributions', 'ui'] });`,
32447
32577
  " ```",
32448
32578
  "",
32449
- `The surface is routable at \`/${p.id}\`. Replace \`view.html\` with your own UI in any framework.`,
32579
+ `The surface is routable at \`/${plugin.id}\`. Replace \`view.html\` with your own UI in any framework.`,
32450
32580
  ""
32451
32581
  ].join("\n");
32452
32582
  }
32453
32583
  var framePlugin = {
32454
32584
  id: "frame-plugin",
32455
32585
  build(input) {
32456
- const p = resolveFramePluginInput(input);
32586
+ const plugin = resolveFramePluginInput(input);
32457
32587
  return {
32458
- "plugin.html": pluginHtml(p),
32459
- "plugin.js": pluginJs(p),
32460
- "view.html": viewHtml(p),
32461
- "README.md": readme(p)
32588
+ "plugin.html": pluginHtml(plugin),
32589
+ "plugin.js": pluginJs(plugin),
32590
+ "view.html": viewHtml(plugin),
32591
+ "README.md": readme(plugin)
32462
32592
  };
32463
32593
  }
32464
32594
  };
32465
32595
 
32466
- // ../devkit/src/recipes/platform-version.ts
32467
- var PLATFORM_VERSION = "0.14.1";
32468
-
32469
32596
  // ../devkit/src/recipes/angular-distribution/amendments.ts
32470
32597
  var DISTRIBUTION_INITIAL_BUDGET = {
32471
32598
  warning: "1.5MB",
32472
32599
  error: "2MB"
32473
32600
  };
32474
- function distributionAmendments(d) {
32601
+ function distributionAmendments(distribution) {
32475
32602
  return [
32476
32603
  {
32477
32604
  kind: "package",
32478
32605
  name: "@loomweaver/frame-kit",
32479
32606
  version: `^${PLATFORM_VERSION}`
32480
32607
  },
32481
- ...d.styles === "tailwind" ? [
32608
+ ...distribution.styles === "tailwind" ? [
32482
32609
  {
32483
32610
  kind: "postcss",
32484
32611
  file: ".postcssrc.json",
@@ -32510,26 +32637,13 @@ function distributionAmendments(d) {
32510
32637
  ];
32511
32638
  }
32512
32639
 
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
32640
  // ../devkit/src/recipes/angular-distribution/logo.ts
32527
32641
  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
32642
  `;
32529
32643
 
32530
32644
  // ../devkit/src/recipes/angular-distribution/readme.ts
32531
- function stylesNotes(d) {
32532
- if (d.styles === "precompiled") {
32645
+ function stylesNotes(distribution) {
32646
+ if (distribution.styles === "precompiled") {
32533
32647
  return [
32534
32648
  "`src/styles.css` imports the stylesheet **we** compiled \u2014 tokens, the `.lw-*` class contracts",
32535
32649
  "and every utility the shell's own templates use, 67 KB minified and 11 KB over the wire. There",
@@ -32567,9 +32681,9 @@ function stylesNotes(d) {
32567
32681
  "Bootstrap, Bulma or hand-written CSS."
32568
32682
  ];
32569
32683
  }
32570
- function readme2(d) {
32684
+ function readme2(distribution) {
32571
32685
  return [
32572
- `# ${d.title} \u2014 a LoomWeaver distribution`,
32686
+ `# ${distribution.title} \u2014 a LoomWeaver distribution`,
32573
32687
  "",
32574
32688
  `The composition root that assembles the platform into a shippable product. It renders the bare`,
32575
32689
  `shell out of the box; add your weavers and branding below. Your own README is untouched \u2014 the`,
@@ -32577,29 +32691,23 @@ function readme2(d) {
32577
32691
  "",
32578
32692
  "## Run it",
32579
32693
  "",
32580
- "In an Nx workspace (the generator wired the project):",
32581
- "",
32582
32694
  "```sh",
32583
- `nx serve ${d.name}`,
32695
+ `nx serve ${distribution.name} # in an Nx workspace`,
32696
+ "ng serve # in an Angular CLI application",
32584
32697
  "```",
32585
32698
  "",
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.",
32699
+ "The files keep Angular's own shape: `main.ts` bootstraps `App`, `App` renders `<lw-shell />`, and",
32700
+ "everything this product is made of lives in `app.config.ts`. Nothing of the generated app is",
32701
+ "deleted. Over an existing application `--force` replaces exactly the files above, all of them",
32702
+ "bootstrap wiring and none of them content you authored. Whether the build around them was wired",
32703
+ 'as well depends on how they arrived; see "Build wiring" below.',
32593
32704
  "",
32594
32705
  "Two leftovers from `ng new` are no longer referenced and can go: `src/app/app.routes.ts` (the",
32595
32706
  "shell owns content routing via `provideShellRouter()`) and `src/app/app.css`. The starter test",
32596
32707
  "`src/app/app.spec.ts` was replaced by one that boots the shell with this composition root, so",
32597
32708
  "`ng test` is green as generated; test your own components beside it.",
32598
32709
  "",
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.",
32710
+ ...UNTAGGED_NOTE,
32603
32711
  "",
32604
32712
  "## Compose weavers + branding (in `src/app/app.config.ts`)",
32605
32713
  "",
@@ -32647,7 +32755,7 @@ function readme2(d) {
32647
32755
  "",
32648
32756
  "```ts",
32649
32757
  "provideCommandPaletteEntry(); // top bar, end slot, order 5",
32650
- "provideQuickOpenEntry({ bar: 'top-bar', order: 4 }); // \u2026or put it wherever you want",
32758
+ `provideQuickOpenEntry({ bar: '${TOP_BAR_REGION}', order: 4 }); // \u2026or put it wherever you want`,
32651
32759
  "```",
32652
32760
  "",
32653
32761
  "A badge never outlives what it opens. Drop the search and its badge goes with it; the same",
@@ -32680,13 +32788,21 @@ function readme2(d) {
32680
32788
  "",
32681
32789
  "## Styles",
32682
32790
  "",
32683
- ...stylesNotes(d),
32791
+ ...stylesNotes(distribution),
32684
32792
  "",
32685
32793
  "## Build wiring",
32686
32794
  "",
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.",
32795
+ "The build target needs the stylesheet, three asset globs, the service worker and two production",
32796
+ "settings. Who added them depends on how these files arrived:",
32797
+ "",
32798
+ "- **The Nx generator** wrote them into the project's `project.json`. Composing over an existing",
32799
+ " project with `--force`, it merged them in value by value: every value the project already had",
32800
+ " stays, and what the workbench needs is added beside it.",
32801
+ "- **The CLI inside an Angular CLI application** added what was missing to the build target in",
32802
+ " `angular.json` and listed each line it added. Anything you had already set was left as you set",
32803
+ " it.",
32804
+ "- **The MCP server**, and the CLI anywhere else, cannot reach your build configuration, so they",
32805
+ " returned these as steps still to do by hand.",
32690
32806
  "",
32691
32807
  "What each is for, so that nobody removes one as clutter. The **`@loomweaver/shell/i18n` glob**",
32692
32808
  "serves the strings the shell fetches at runtime; without it every label in the chrome renders as",
@@ -32746,7 +32862,7 @@ function readme2(d) {
32746
32862
  }
32747
32863
 
32748
32864
  // ../devkit/src/recipes/angular-distribution/recipe.ts
32749
- var STYLES = ["tailwind", "precompiled"];
32865
+ var DISTRIBUTION_STYLES = ["tailwind", "precompiled"];
32750
32866
  function resolveDistributionInput(input) {
32751
32867
  if (!isKebabId(input.name)) {
32752
32868
  throw new Error(
@@ -32754,9 +32870,9 @@ function resolveDistributionInput(input) {
32754
32870
  );
32755
32871
  }
32756
32872
  const styles = input.styles ?? "tailwind";
32757
- if (!STYLES.includes(styles)) {
32873
+ if (!DISTRIBUTION_STYLES.includes(styles)) {
32758
32874
  throw new Error(
32759
- `Unknown styles option "${styles}"; expected one of ${STYLES.join(", ")}.`
32875
+ `Unknown styles option "${styles}"; expected one of ${DISTRIBUTION_STYLES.join(", ")}.`
32760
32876
  );
32761
32877
  }
32762
32878
  const directory = input.directory === void 0 ? `apps/${input.name}` : input.directory.trim();
@@ -32777,7 +32893,7 @@ import { App } from './app/app';
32777
32893
  bootstrapApplication(App, appConfig).catch((err) => console.error(err));
32778
32894
  `;
32779
32895
  }
32780
- function appConfigTs(d) {
32896
+ function appConfigTs(distribution) {
32781
32897
  return `import { ApplicationConfig } from '@angular/core';
32782
32898
  import {
32783
32899
  provideCommandPaletteEntry,
@@ -32790,8 +32906,8 @@ import {
32790
32906
  import { provideProductIdentity } from '@loomweaver/plugin-sdk';
32791
32907
 
32792
32908
  /* 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. */
32909
+ weaver names but this layout omits renders nothing \u2014 silently. '${RAIL_REGION}' (rail) and
32910
+ '${STATUS_BAR_REGION}' (bar) are what the scaffolded weaver targets. */
32795
32911
  export const layout: ShellLayout = {
32796
32912
  regions: [
32797
32913
  ${renderRegions(" ".repeat(4))}
@@ -32813,7 +32929,7 @@ export const appConfig: ApplicationConfig = {
32813
32929
  provideCommandPaletteEntry(),
32814
32930
  provideQuickOpenEntry(),
32815
32931
  provideProductIdentity({
32816
- name: '${d.title}',
32932
+ name: '${distribution.title}',
32817
32933
  tagline: 'Built on LoomWeaver',
32818
32934
  logoUrl: 'logo.svg',
32819
32935
  }),
@@ -32846,7 +32962,7 @@ function appConfigSpec() {
32846
32962
  describe('layout', () => {
32847
32963
  it('declares the regions contributions target', () => {
32848
32964
  const ids = layout.regions.map((region) => region.id);
32849
- for (const id of ['primary', 'status-bar', 'main']) {
32965
+ for (const id of ['${RAIL_REGION}', '${STATUS_BAR_REGION}', '${CONTENT_REGION}']) {
32850
32966
  expect(ids).toContain(id);
32851
32967
  }
32852
32968
  });
@@ -32878,12 +32994,12 @@ describe('App', () => {
32878
32994
  });
32879
32995
  `;
32880
32996
  }
32881
- function indexHtml(d) {
32997
+ function indexHtml(distribution) {
32882
32998
  return `<!DOCTYPE html>
32883
32999
  <html lang="en">
32884
33000
  <head>
32885
33001
  <meta charset="utf-8" />
32886
- <title>${d.title}</title>
33002
+ <title>${distribution.title}</title>
32887
33003
  <base href="/" />
32888
33004
  <meta
32889
33005
  http-equiv="Content-Security-Policy"
@@ -32921,7 +33037,7 @@ function precompiledCss() {
32921
33037
  @import '@loomweaver/shell/styles/shell.css';
32922
33038
  `;
32923
33039
  }
32924
- function tailwindCss(d) {
33040
+ function tailwindCss(distribution) {
32925
33041
  return `@import 'tailwindcss';
32926
33042
 
32927
33043
  /* LoomWeaver design tokens + theme (light/dark, brand colors). Every @import has to precede the
@@ -32934,12 +33050,12 @@ function tailwindCss(d) {
32934
33050
  /* Generate the utility classes the shell (and your own components) use. The @source path must
32935
33051
  reach your workspace's node_modules FROM THIS FILE \u2014 adjust the ../ hops if this project does
32936
33052
  not sit at that depth, or Tailwind silently emits none of the shell's classes. */
32937
- @source '${d.nodeModulesFromSrc}/@loomweaver/shell';
33053
+ @source '${distribution.nodeModulesFromSrc}/@loomweaver/shell';
32938
33054
  @source './';
32939
33055
  `;
32940
33056
  }
32941
- function stylesCss(d) {
32942
- return d.styles === "precompiled" ? precompiledCss() : tailwindCss(d);
33057
+ function stylesCss(distribution) {
33058
+ return distribution.styles === "precompiled" ? precompiledCss() : tailwindCss(distribution);
32943
33059
  }
32944
33060
  function ngswConfig() {
32945
33061
  return JSON.stringify(
@@ -32978,12 +33094,12 @@ function ngswConfig() {
32978
33094
  2
32979
33095
  );
32980
33096
  }
32981
- function manifest(d) {
33097
+ function manifest(distribution) {
32982
33098
  return JSON.stringify(
32983
33099
  {
32984
- name: d.title,
32985
- short_name: d.title,
32986
- description: `${d.title} \u2014 a LoomWeaver distribution.`,
33100
+ name: distribution.title,
33101
+ short_name: distribution.title,
33102
+ description: `${distribution.title} \u2014 a LoomWeaver distribution.`,
32987
33103
  start_url: "/",
32988
33104
  display: "standalone",
32989
33105
  background_color: "#ffffff",
@@ -33000,22 +33116,22 @@ var angularDistribution = {
33000
33116
  return distributionAmendments(resolveDistributionInput(input));
33001
33117
  },
33002
33118
  build(input) {
33003
- const d = resolveDistributionInput(input);
33119
+ const distribution = resolveDistributionInput(input);
33004
33120
  return {
33005
33121
  "src/main.ts": mainTs(),
33006
- "src/app/app.config.ts": appConfigTs(d),
33007
- ...d.withTests && {
33122
+ "src/app/app.config.ts": appConfigTs(distribution),
33123
+ ...distribution.withTests && {
33008
33124
  "src/app/app.config.spec.ts": appConfigSpec(),
33009
33125
  "src/app/app.spec.ts": appSpec()
33010
33126
  },
33011
33127
  "src/app/app.ts": appTs(),
33012
33128
  "src/app/app.html": appHtml(),
33013
- "src/index.html": indexHtml(d),
33014
- "src/styles.css": stylesCss(d),
33129
+ "src/index.html": indexHtml(distribution),
33130
+ "src/styles.css": stylesCss(distribution),
33015
33131
  "public/logo.svg": PLACEHOLDER_LOGO_SVG,
33016
- "public/manifest.webmanifest": manifest(d) + "\n",
33132
+ "public/manifest.webmanifest": manifest(distribution) + "\n",
33017
33133
  "ngsw-config.json": ngswConfig() + "\n",
33018
- "LOOMWEAVER.md": readme2(d)
33134
+ "LOOMWEAVER.md": readme2(distribution)
33019
33135
  };
33020
33136
  }
33021
33137
  };
@@ -33033,12 +33149,12 @@ function resolveAuthSourceInput(input) {
33033
33149
  bare: input.bare ?? false
33034
33150
  };
33035
33151
  }
33036
- function sessionPluginSymbol(a) {
33037
- return `${a.propertyName}SessionPlugin`;
33152
+ function sessionPluginSymbol(source) {
33153
+ return `${source.propertyName}SessionPlugin`;
33038
33154
  }
33039
- function moduleFile(a) {
33155
+ function moduleFile(source) {
33040
33156
  return `// Provider-neutral AuthSource. LoomWeaver owns no authentication \u2014 it only reacts to
33041
- // a session snapshot. Wire it with: provideAuthSource(() => ${a.propertyName}AuthSource()).
33157
+ // a session snapshot. Wire it with: provideAuthSource(() => ${source.propertyName}AuthSource()).
33042
33158
  // Replace the dev switcher below by mapping your product's real session onto an AuthSnapshot.
33043
33159
  import { signal, Signal } from '@angular/core';
33044
33160
  import { ANONYMOUS, AuthSnapshot } from '@loomweaver/plugin-sdk';
@@ -33059,52 +33175,40 @@ const ADMIN: AuthSnapshot = {
33059
33175
 
33060
33176
  const state = signal<AuthSnapshot>(ANONYMOUS);
33061
33177
 
33062
- export function ${a.propertyName}AuthSource(): Signal<AuthSnapshot> {
33178
+ export function ${source.propertyName}AuthSource(): Signal<AuthSnapshot> {
33063
33179
  return state.asReadonly();
33064
33180
  }
33065
33181
 
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);
33182
+ export function signIn${source.className}User(): void {
33183
+ state.set(USER);
33184
+ }
33185
+
33186
+ export function switch${source.className}Account(): void {
33187
+ state.set(state().roles.includes('admin') ? USER : ADMIN);
33188
+ }
33189
+
33190
+ export function signOut${source.className}User(): void {
33191
+ state.set(ANONYMOUS);
33074
33192
  }
33075
33193
  `;
33076
33194
  }
33077
- function pluginFile2(a) {
33195
+ function pluginFile2(source) {
33078
33196
  return `// The verbs a user needs to operate the stand-in session: sign in, switch the account, sign
33079
33197
  // out, from a rail item whose menu carries them. This is presentation for a product that has no
33080
33198
  // backend yet; nothing here protects anything. Delete it once your own session arrives.
33081
33199
  import type { Disposable, Plugin, PluginContext } from '@loomweaver/plugin-sdk';
33082
- import { cycle${a.className}User, ${a.propertyName}AuthSource } from './${a.name}-auth-source';
33200
+ import {
33201
+ ${source.propertyName}AuthSource,
33202
+ signIn${source.className}User,
33203
+ signOut${source.className}User,
33204
+ switch${source.className}Account,
33205
+ } from './${source.name}-auth-source';
33083
33206
 
33084
- const MENU = 'session.account/menu';
33085
- const snapshot = ${a.propertyName}AuthSource();
33207
+ const MENU = '${SESSION_PLUGIN_ID}.account/menu';
33208
+ const snapshot = ${source.propertyName}AuthSource();
33086
33209
 
33087
33210
  let drawn: Disposable[] = [];
33088
33211
 
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
33212
  function initialsOf(name: string): string {
33109
33213
  return name
33110
33214
  .split(' ')
@@ -33123,7 +33227,7 @@ function draw(ctx: PluginContext): void {
33123
33227
  drawn = [
33124
33228
  ctx.registerRailItem({
33125
33229
  id: 'session.account',
33126
- rail: 'primary',
33230
+ rail: '${RAIL_REGION}',
33127
33231
  icon: 'account',
33128
33232
  title: current.authenticated ? name : 'session.signIn',
33129
33233
  anchor: 'bottom',
@@ -33168,10 +33272,10 @@ function draw(ctx: PluginContext): void {
33168
33272
  ];
33169
33273
  }
33170
33274
 
33171
- export const ${sessionPluginSymbol(a)}: Plugin = {
33275
+ export const ${sessionPluginSymbol(source)}: Plugin = {
33172
33276
  manifest: { id: 'session', name: 'Account', capabilities: ['contributions'] },
33173
33277
  activate(ctx) {
33174
- const then = (step: () => void) => () => {
33278
+ const andRedraw = (step: () => void) => () => {
33175
33279
  step();
33176
33280
  draw(ctx);
33177
33281
  };
@@ -33180,21 +33284,21 @@ export const ${sessionPluginSymbol(a)}: Plugin = {
33180
33284
  title: 'session.signIn',
33181
33285
  icon: 'account',
33182
33286
  access: { authenticated: false },
33183
- run: then(signIn),
33287
+ run: andRedraw(signIn${source.className}User),
33184
33288
  });
33185
33289
  ctx.registerCommand({
33186
33290
  id: 'session.switchAccount',
33187
33291
  title: 'session.switchAccount',
33188
33292
  icon: 'account',
33189
33293
  access: { authenticated: true },
33190
- run: then(switchAccount),
33294
+ run: andRedraw(switch${source.className}Account),
33191
33295
  });
33192
33296
  ctx.registerCommand({
33193
33297
  id: 'session.signOut',
33194
33298
  title: 'session.signOut',
33195
33299
  icon: 'signOut',
33196
33300
  access: { authenticated: true },
33197
- run: then(signOut),
33301
+ run: andRedraw(signOut${source.className}User),
33198
33302
  });
33199
33303
  draw(ctx);
33200
33304
  },
@@ -33207,9 +33311,14 @@ export const ${sessionPluginSymbol(a)}: Plugin = {
33207
33311
  };
33208
33312
  `;
33209
33313
  }
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';
33314
+ function indexFile2(source) {
33315
+ return `export {
33316
+ ${source.propertyName}AuthSource,
33317
+ signIn${source.className}User,
33318
+ signOut${source.className}User,
33319
+ switch${source.className}Account,
33320
+ } from './${source.name}-auth-source';
33321
+ export { ${sessionPluginSymbol(source)} } from './${source.name}-session.plugin';
33213
33322
  `;
33214
33323
  }
33215
33324
  var I18N = {
@@ -33231,15 +33340,15 @@ var I18N = {
33231
33340
  var authSource = {
33232
33341
  id: "auth-source",
33233
33342
  build(input) {
33234
- const a = resolveAuthSourceInput(input);
33235
- const source = { [`${a.name}-auth-source.ts`]: moduleFile(a) };
33236
- if (a.bare) {
33237
- return source;
33343
+ const source = resolveAuthSourceInput(input);
33344
+ const moduleFiles = { [`${source.name}-auth-source.ts`]: moduleFile(source) };
33345
+ if (source.bare) {
33346
+ return moduleFiles;
33238
33347
  }
33239
33348
  return {
33240
- ...source,
33241
- [`${a.name}-session.plugin.ts`]: pluginFile2(a),
33242
- "index.ts": indexFile2(a),
33349
+ ...moduleFiles,
33350
+ [`${source.name}-session.plugin.ts`]: pluginFile2(source),
33351
+ "index.ts": indexFile2(source),
33243
33352
  "i18n/en.json": `${JSON.stringify(I18N.en, null, 2)}
33244
33353
  `,
33245
33354
  "i18n/de.json": `${JSON.stringify(I18N.de, null, 2)}
@@ -33257,14 +33366,14 @@ function resolveSettingsStoreInput(input) {
33257
33366
  }
33258
33367
  return { name: input.name, className: toPascalCase(input.name) };
33259
33368
  }
33260
- function moduleFile2(s) {
33369
+ function moduleFile2(store) {
33261
33370
  return `// A backend-backed settings store for the SETTINGS_STORE port. The platform
33262
33371
  // ships local defaults; the product persists settings against its own backend. Only genuine
33263
33372
  // settings flow through this port \u2014 working state stays on WORKING_STATE_STORE. Wire it with:
33264
- // provideSettingsStore(new ${s.className}SettingsStore()).
33373
+ // provideSettingsStore(new ${store.className}SettingsStore()).
33265
33374
  import { KeyValueStore } from '@loomweaver/shell';
33266
33375
 
33267
- export class ${s.className}SettingsStore implements KeyValueStore {
33376
+ export class ${store.className}SettingsStore implements KeyValueStore {
33268
33377
  constructor(private readonly baseUrl = '/api/settings') {}
33269
33378
 
33270
33379
  async get(key: string): Promise<string | undefined> {
@@ -33292,28 +33401,28 @@ export class ${s.className}SettingsStore implements KeyValueStore {
33292
33401
  var settingsStore = {
33293
33402
  id: "settings-store",
33294
33403
  build(input) {
33295
- const s = resolveSettingsStoreInput(input);
33296
- return { [`${s.name}-settings-store.ts`]: moduleFile2(s) };
33404
+ const store = resolveSettingsStoreInput(input);
33405
+ return { [`${store.name}-settings-store.ts`]: moduleFile2(store) };
33297
33406
  }
33298
33407
  };
33299
33408
 
33300
33409
  // ../devkit/src/recipes/theme/recipe.ts
33301
- var PRESETS = ["literal", "bootstrap"];
33410
+ var THEME_PRESETS = ["literal", "bootstrap"];
33302
33411
  function resolveThemeInput(input) {
33303
33412
  if (!isKebabId(input.name)) {
33304
33413
  throw new Error(`Theme name must be kebab-case (e.g. "midnight"); got "${input.name}".`);
33305
33414
  }
33306
33415
  const preset = input.preset ?? "literal";
33307
- if (!PRESETS.includes(preset)) {
33308
- throw new Error(`Unknown theme preset "${preset}"; expected one of ${PRESETS.join(", ")}.`);
33416
+ if (!THEME_PRESETS.includes(preset)) {
33417
+ throw new Error(`Unknown theme preset "${preset}"; expected one of ${THEME_PRESETS.join(", ")}.`);
33309
33418
  }
33310
33419
  return { name: input.name, title: toTitleCase(input.name), preset };
33311
33420
  }
33312
- function cssFile(t) {
33313
- return `/* ${t.title} theme \u2014 overrides LoomWeaver's --lw-* design tokens.
33421
+ function cssFile(theme2) {
33422
+ return `/* ${theme2.title} theme \u2014 overrides LoomWeaver's --lw-* design tokens.
33314
33423
  Import it AFTER the shell theme in your distribution styles.css:
33315
33424
  @import '@loomweaver/shell/styles/theme.css';
33316
- @import './themes/${t.name}.css';
33425
+ @import './themes/${theme2.name}.css';
33317
33426
  Declared in @layer lw-tenant-theme so it beats any plugin's ctx.contributeTheme
33318
33427
  (Product default < Plugin < Tenant). The --lw-* ladder flips in :root.dark, so override both. */
33319
33428
 
@@ -33348,15 +33457,15 @@ function cssFile(t) {
33348
33457
  }
33349
33458
  `;
33350
33459
  }
33351
- function bootstrapFile(t) {
33352
- return `/* ${t.title} theme \u2014 maps LoomWeaver's --lw-* design tokens onto Bootstrap 5.3's --bs-*.
33460
+ function bootstrapFile(theme2) {
33461
+ return `/* ${theme2.title} theme \u2014 maps LoomWeaver's --lw-* design tokens onto Bootstrap 5.3's --bs-*.
33353
33462
  Import it AFTER the shell theme in your distribution styles.css, and import Bootstrap itself
33354
33463
  INTO A LAYER \u2014 unlayered CSS outranks layered CSS whatever its specificity, so Bootstrap's
33355
33464
  Reboot (button { border-radius: 0 }) would otherwise beat our .lw-* component classes:
33356
33465
  @layer vendor;
33357
33466
  @import 'bootstrap/dist/css/bootstrap.css' layer(vendor);
33358
33467
  @import '@loomweaver/shell/styles/shell.css'; (or styles/theme.css if you run Tailwind)
33359
- @import './themes/${t.name}.css';
33468
+ @import './themes/${theme2.name}.css';
33360
33469
  Declared in @layer lw-tenant-theme so it beats any plugin's ctx.contributeTheme
33361
33470
  (Product default < Plugin < Tenant).
33362
33471
 
@@ -33431,9 +33540,9 @@ function bootstrapFile(t) {
33431
33540
  var theme = {
33432
33541
  id: "theme",
33433
33542
  build(input) {
33434
- const t = resolveThemeInput(input);
33435
- const content = t.preset === "bootstrap" ? bootstrapFile(t) : cssFile(t);
33436
- return { [`${t.name}.css`]: content };
33543
+ const theme2 = resolveThemeInput(input);
33544
+ const content = theme2.preset === "bootstrap" ? bootstrapFile(theme2) : cssFile(theme2);
33545
+ return { [`${theme2.name}.css`]: content };
33437
33546
  }
33438
33547
  };
33439
33548
 
@@ -33445,13 +33554,13 @@ function resolveLayoutInput(input) {
33445
33554
  }
33446
33555
  return { name, propertyName: toCamelCase(name) };
33447
33556
  }
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
33557
+ function moduleFile3(layout2) {
33558
+ return `// A base layout for the distribution. Pass it to provideLayout(${layout2.propertyName}Layout)
33559
+ // in src/app/app.config.ts. Region ids are what contributions target \u2014 '${RAIL_REGION}' (rail) and '${STATUS_BAR_REGION}' (bar) match
33451
33560
  // the devkit weaver defaults, so a scaffolded weaver's rail + bar items land here.
33452
33561
  import { ShellLayout } from '@loomweaver/shell';
33453
33562
 
33454
- export const ${l.propertyName}Layout: ShellLayout = {
33563
+ export const ${layout2.propertyName}Layout: ShellLayout = {
33455
33564
  regions: [
33456
33565
  ${renderRegions(" ".repeat(4))}
33457
33566
  ],
@@ -33461,19 +33570,70 @@ ${renderRegions(" ".repeat(4))}
33461
33570
  var layout = {
33462
33571
  id: "layout",
33463
33572
  build(input) {
33464
- const l = resolveLayoutInput(input);
33465
- return { [`${l.name}-layout.ts`]: moduleFile3(l) };
33573
+ const layout2 = resolveLayoutInput(input);
33574
+ return { [`${layout2.name}-layout.ts`]: moduleFile3(layout2) };
33466
33575
  }
33467
33576
  };
33468
33577
 
33578
+ // ../devkit/src/recipes/angular-distribution/scaffold.ts
33579
+ function distributionInput(values) {
33580
+ return {
33581
+ name: stringValue(values, "name") ?? "",
33582
+ title: stringValue(values, "title"),
33583
+ directory: stringValue(values, "directory"),
33584
+ styles: stringValue(values, "styles") ?? "tailwind"
33585
+ };
33586
+ }
33587
+ var distributionScaffold = {
33588
+ name: "distribution",
33589
+ summary: "a runnable composition root that boots the shell",
33590
+ options: [
33591
+ {
33592
+ name: "name",
33593
+ type: "string",
33594
+ description: "Distribution name in kebab-case, e.g. 'acme-studio'.",
33595
+ required: true,
33596
+ pattern: KEBAB_ID_PATTERN
33597
+ },
33598
+ {
33599
+ name: "title",
33600
+ type: "string",
33601
+ description: "Product display title. Defaults to a title-cased name."
33602
+ },
33603
+ {
33604
+ name: "styles",
33605
+ type: "string",
33606
+ 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.",
33607
+ choices: DISTRIBUTION_STYLES,
33608
+ default: "tailwind"
33609
+ },
33610
+ {
33611
+ name: "force",
33612
+ type: "boolean",
33613
+ 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.",
33614
+ workspaceOnly: true
33615
+ },
33616
+ {
33617
+ name: "prefix",
33618
+ type: "string",
33619
+ description: "Selector prefix the application declares for its components, which a weaver generated into it takes. Defaults to 'app'.",
33620
+ pattern: KEBAB_ID_PATTERN,
33621
+ workspaceOnly: true
33622
+ },
33623
+ ...PLACEMENT_OPTIONS
33624
+ ],
33625
+ build: (values) => generate(angularDistribution, distributionInput(values)),
33626
+ amend: (values) => amendments(angularDistribution, distributionInput(values))
33627
+ };
33628
+
33469
33629
  // ../devkit/src/recipes/angular-weaver/amendments.ts
33470
33630
  function weaverAmendments(input, where) {
33471
- const w = resolveWeaverInput(input);
33472
- const packages = w.features.agent ? [
33631
+ const weaver = resolveWeaverInput(input);
33632
+ const packages = weaver.features.agent ? [
33473
33633
  {
33474
33634
  kind: "package",
33475
33635
  name: "@loomweaver/ag-ui",
33476
- version: `^${AG_UI_ADAPTER_VERSION}`
33636
+ version: `^${PLATFORM_VERSION}`
33477
33637
  },
33478
33638
  {
33479
33639
  kind: "package",
@@ -33495,25 +33655,151 @@ function weaverAmendments(input, where) {
33495
33655
  glob: "**/*.json",
33496
33656
  input: joinProjectPath(directory, "src/lib/i18n"),
33497
33657
  from: "workspace",
33498
- output: `i18n/${w.id}`
33658
+ output: `i18n/${weaver.id}`
33499
33659
  }
33500
33660
  ]
33501
33661
  },
33502
33662
  { kind: "stylesheet-source", sourceRoot: joinProjectPath(directory, "src") },
33503
33663
  {
33504
33664
  kind: "compose-plugin",
33505
- id: w.id,
33506
- symbol: `${w.propertyName}Plugin`,
33507
- capabilities: w.capabilities,
33665
+ id: weaver.id,
33666
+ symbol: `${weaver.propertyName}Plugin`,
33667
+ capabilities: weaver.capabilities,
33508
33668
  sourceRoot: joinProjectPath(directory, "src")
33509
33669
  }
33510
33670
  ];
33511
33671
  }
33512
33672
 
33513
- // ../devkit/src/recipes/auth-source/recipe-amendments.ts
33673
+ // ../devkit/src/recipes/angular-weaver/scaffold.ts
33674
+ function weaverInput(values) {
33675
+ return {
33676
+ id: stringValue(values, "id") ?? "",
33677
+ name: stringValue(values, "name"),
33678
+ prefix: stringValue(values, "prefix"),
33679
+ importPath: stringValue(values, "importPath"),
33680
+ features: {
33681
+ command: booleanValue(values, "command"),
33682
+ shortcut: stringValue(values, "shortcut"),
33683
+ menu: stringValue(values, "menu"),
33684
+ barItem: booleanValue(values, "barItem"),
33685
+ settings: booleanValue(values, "settings"),
33686
+ about: booleanValue(values, "about"),
33687
+ instanceable: booleanValue(values, "instanceable"),
33688
+ container: booleanValue(values, "container"),
33689
+ agent: booleanValue(values, "agent"),
33690
+ access: stringValue(values, "access"),
33691
+ spec: booleanValue(values, "spec")
33692
+ }
33693
+ };
33694
+ }
33695
+ var weaverScaffold = {
33696
+ name: "weaver",
33697
+ summary: "an Angular in-process plugin: manifest, surface, rail item, i18n, test",
33698
+ options: [
33699
+ {
33700
+ name: "id",
33701
+ type: "string",
33702
+ description: "Plugin id in kebab-case, e.g. 'notes'.",
33703
+ required: true,
33704
+ pattern: KEBAB_ID_PATTERN
33705
+ },
33706
+ {
33707
+ name: "name",
33708
+ type: "string",
33709
+ description: "Human-readable name. Defaults to a title-cased id."
33710
+ },
33711
+ {
33712
+ name: "command",
33713
+ type: "boolean",
33714
+ description: "Also scaffold a registered command the weaver can trigger.",
33715
+ default: false
33716
+ },
33717
+ {
33718
+ name: "shortcut",
33719
+ type: "string",
33720
+ description: "Keyboard chord for the command, e.g. 'mod+shift+k'. Implies --command."
33721
+ },
33722
+ {
33723
+ name: "menu",
33724
+ type: "string",
33725
+ description: "Hook a menu item into a slot, e.g. 'content/tab/context'. Implies --command."
33726
+ },
33727
+ {
33728
+ name: "barItem",
33729
+ type: "boolean",
33730
+ description: "Also add a status-bar button that triggers the command. Implies --command.",
33731
+ default: false
33732
+ },
33733
+ {
33734
+ name: "settings",
33735
+ type: "boolean",
33736
+ description: "Also scaffold a settings section with signal-backed value owners.",
33737
+ default: false
33738
+ },
33739
+ {
33740
+ name: "about",
33741
+ type: "boolean",
33742
+ description: "Also scaffold an About dialog that reads ctx.host, plus a bottom rail item.",
33743
+ default: false
33744
+ },
33745
+ {
33746
+ name: "instanceable",
33747
+ type: "boolean",
33748
+ 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.",
33749
+ default: false
33750
+ },
33751
+ {
33752
+ name: "container",
33753
+ type: "boolean",
33754
+ 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.",
33755
+ default: false
33756
+ },
33757
+ {
33758
+ name: "agent",
33759
+ type: "boolean",
33760
+ 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.",
33761
+ default: false
33762
+ },
33763
+ {
33764
+ name: "access",
33765
+ type: "string",
33766
+ description: "Auth-gate the surface and rail item: 'authenticated', 'anonymous' or 'role:<name>'."
33767
+ },
33768
+ {
33769
+ name: "spec",
33770
+ type: "boolean",
33771
+ description: "Generate a starter unit test. Use --no-spec to skip it.",
33772
+ default: true
33773
+ },
33774
+ {
33775
+ name: "projectName",
33776
+ type: "string",
33777
+ description: "Nx project name. Defaults to '<id>-weaver'.",
33778
+ workspaceOnly: true
33779
+ },
33780
+ {
33781
+ name: "importPath",
33782
+ type: "string",
33783
+ description: "Import path for the workspace alias. Defaults to the workspace scope plus the project name.",
33784
+ workspaceOnly: true
33785
+ },
33786
+ {
33787
+ name: "prefix",
33788
+ type: "string",
33789
+ description: "Selector prefix for the generated components: the one the application declares, as the prefix of its project in angular.json or project.json. Read from the workspace where it can be, else 'app'.",
33790
+ pattern: KEBAB_ID_PATTERN
33791
+ },
33792
+ APP_OPTION,
33793
+ ...PLACEMENT_OPTIONS
33794
+ ],
33795
+ build: (values) => generate(angularWeaver, weaverInput(values)),
33796
+ amend: (values) => weaverAmendments(weaverInput(values), stringValue(values, "directory"))
33797
+ };
33798
+
33799
+ // ../devkit/src/recipes/auth-source/amendments.ts
33514
33800
  function authSourceAmendments(input, where) {
33515
- const a = resolveAuthSourceInput(input);
33516
- if (a.bare || where === void 0 || where === "") {
33801
+ const source = resolveAuthSourceInput(input);
33802
+ if (source.bare || where === void 0 || where === "") {
33517
33803
  return [];
33518
33804
  }
33519
33805
  const directory = normalizeProjectRoot(where);
@@ -33533,14 +33819,14 @@ function authSourceAmendments(input, where) {
33533
33819
  {
33534
33820
  kind: "compose-plugin",
33535
33821
  id: SESSION_PLUGIN_ID,
33536
- symbol: sessionPluginSymbol(a),
33822
+ symbol: sessionPluginSymbol(source),
33537
33823
  capabilities: ["contributions"],
33538
33824
  sourceRoot: directory,
33539
33825
  providers: [
33540
33826
  {
33541
- line: `provideAuthSource(() => ${a.propertyName}AuthSource()),`,
33827
+ line: `provideAuthSource(() => ${source.propertyName}AuthSource()),`,
33542
33828
  shell: ["provideAuthSource"],
33543
- own: [`${a.propertyName}AuthSource`],
33829
+ own: [`${source.propertyName}AuthSource`],
33544
33830
  unless: "provideAuthSource("
33545
33831
  },
33546
33832
  {
@@ -33558,325 +33844,135 @@ function authSourceAmendments(input, where) {
33558
33844
  ];
33559
33845
  }
33560
33846
 
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
- };
33847
+ // ../devkit/src/recipes/auth-source/scaffold.ts
33848
+ function authSourceInput(values) {
33849
+ return { name: stringValue(values, "name") ?? "", bare: booleanValue(values, "bare") };
33590
33850
  }
33851
+ var authSourceScaffold = {
33852
+ name: "auth-source",
33853
+ summary: "a stand-in session a user can operate: the AuthSource, the sign-in, switch and sign-out verbs in the rail, composed in",
33854
+ options: [
33855
+ {
33856
+ name: "name",
33857
+ type: "string",
33858
+ description: "Source name in kebab-case, e.g. 'dev'.",
33859
+ required: true,
33860
+ pattern: KEBAB_ID_PATTERN
33861
+ },
33862
+ {
33863
+ name: "bare",
33864
+ type: "boolean",
33865
+ description: "Write the AuthSource alone, without the verbs, for a product that maps a session of its own onto it.",
33866
+ default: false
33867
+ },
33868
+ APP_OPTION,
33869
+ {
33870
+ name: "directory",
33871
+ type: "string",
33872
+ description: "Where the files land, relative to the workspace root.",
33873
+ workspaceOnly: true
33874
+ }
33875
+ ],
33876
+ build: (values) => generate(authSource, authSourceInput(values)),
33877
+ amend: (values) => authSourceAmendments(authSourceInput(values), stringValue(values, "directory"))
33878
+ };
33591
33879
 
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
33880
+ // ../devkit/src/recipes/frame-plugin/scaffold.ts
33881
+ var framePluginScaffold = {
33882
+ name: "frame-plugin",
33883
+ summary: "a framework-agnostic iframe plugin (Penpal + the frame UI kit)",
33884
+ options: [
33885
+ {
33886
+ name: "id",
33887
+ type: "string",
33888
+ description: "Plugin id in kebab-case, e.g. 'notes'.",
33889
+ required: true,
33890
+ pattern: KEBAB_ID_PATTERN
33891
+ },
33892
+ {
33893
+ name: "name",
33894
+ type: "string",
33895
+ description: "Human-readable name. Defaults to a title-cased id."
33896
+ },
33897
+ APP_OPTION
33898
+ ],
33899
+ build: (values) => generate(framePlugin, {
33900
+ id: stringValue(values, "id") ?? "",
33901
+ name: stringValue(values, "name")
33902
+ })
33637
33903
  };
33638
- function authSourceInput(values) {
33639
- return { name: str(values, "name") ?? "", bare: bool(values, "bare") };
33640
- }
33904
+
33905
+ // ../devkit/src/recipes/layout/scaffold.ts
33906
+ var layoutScaffold = {
33907
+ name: "layout",
33908
+ summary: "a ShellLayout with the regions a weaver expects",
33909
+ options: [
33910
+ {
33911
+ name: "name",
33912
+ type: "string",
33913
+ description: "Layout name. Defaults to a base layout."
33914
+ },
33915
+ APP_OPTION
33916
+ ],
33917
+ build: (values) => generate(layout, { name: stringValue(values, "name") })
33918
+ };
33919
+
33920
+ // ../devkit/src/recipes/settings-store/scaffold.ts
33921
+ var settingsStoreScaffold = {
33922
+ name: "settings-store",
33923
+ summary: "a settings-store implementation backed by your API",
33924
+ options: [
33925
+ {
33926
+ name: "name",
33927
+ type: "string",
33928
+ description: "Store name in kebab-case, e.g. 'backend'.",
33929
+ required: true,
33930
+ pattern: KEBAB_ID_PATTERN
33931
+ },
33932
+ APP_OPTION
33933
+ ],
33934
+ build: (values) => generate(settingsStore, { name: stringValue(values, "name") ?? "" })
33935
+ };
33936
+
33937
+ // ../devkit/src/recipes/theme/scaffold.ts
33938
+ var themeScaffold = {
33939
+ name: "theme",
33940
+ summary: "a token-override stylesheet in @layer lw-tenant-theme",
33941
+ options: [
33942
+ {
33943
+ name: "name",
33944
+ type: "string",
33945
+ description: "Theme name in kebab-case, e.g. 'midnight'.",
33946
+ required: true,
33947
+ pattern: KEBAB_ID_PATTERN
33948
+ },
33949
+ {
33950
+ name: "preset",
33951
+ type: "string",
33952
+ 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.",
33953
+ choices: THEME_PRESETS,
33954
+ default: "literal"
33955
+ },
33956
+ APP_OPTION
33957
+ ],
33958
+ build: (values) => generate(theme, {
33959
+ name: stringValue(values, "name") ?? "",
33960
+ preset: stringValue(values, "preset") ?? "literal"
33961
+ })
33962
+ };
33963
+
33964
+ // ../devkit/src/lib/scaffolds/scaffolds.ts
33641
33965
  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
- }
33966
+ weaverScaffold,
33967
+ framePluginScaffold,
33968
+ distributionScaffold,
33969
+ authSourceScaffold,
33970
+ settingsStoreScaffold,
33971
+ themeScaffold,
33972
+ layoutScaffold
33877
33973
  ];
33878
33974
 
33879
- // ../devkit/src/lib/scaffolds/surface.ts
33975
+ // ../devkit/src/lib/scaffolds/adapter-options.ts
33880
33976
  function portableOptions(scaffold2) {
33881
33977
  return scaffold2.options.filter((option) => !option.workspaceOnly);
33882
33978
  }
@@ -33938,41 +34034,50 @@ var CATALOG_ENTRY_KEYS = [
33938
34034
  "readmeUrl"
33939
34035
  ];
33940
34036
  var SAME_ORIGIN_FIELDS = ["entryUrl", "iconUrl", "readmeUrl"];
33941
- function at(index, field) {
34037
+ function pathOf(index, field) {
33942
34038
  return field ? `catalog[${index}].${field}` : `catalog[${index}]`;
33943
34039
  }
33944
34040
  function isPlainObject3(raw) {
33945
34041
  return typeof raw === "object" && raw !== null && !Array.isArray(raw);
33946
34042
  }
33947
- function urlFinding(value, index, field, required2) {
34043
+ function requiredUrlFinding(value, index, field) {
33948
34044
  if (typeof value !== "string" || value.length === 0) {
33949
- return required2 ? {
34045
+ return {
33950
34046
  level: "error",
33951
34047
  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
- } : {
34048
+ message: `${pathOf(index, field)} must be a non-empty string; the host drops the whole entry without it.`,
34049
+ path: pathOf(index, field)
34050
+ };
34051
+ }
34052
+ return schemeFinding(value, index, field, "drops the entry");
34053
+ }
34054
+ function optionalUrlFinding(value, index, field) {
34055
+ if (typeof value !== "string" || value.length === 0) {
34056
+ return {
33955
34057
  level: "warning",
33956
34058
  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)
34059
+ message: `${pathOf(index, field)} is present but not a non-empty string, so the host ignores it.`,
34060
+ path: pathOf(index, field)
33959
34061
  };
33960
34062
  }
34063
+ return schemeFinding(value, index, field, "ignores the field");
34064
+ }
34065
+ function schemeFinding(value, index, field, refusal) {
33961
34066
  const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value)?.[1]?.toLowerCase();
33962
34067
  if (scheme && scheme !== "http" && scheme !== "https") {
33963
34068
  return {
33964
34069
  level: "error",
33965
34070
  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)
34071
+ message: `${pathOf(index, field)} uses the "${scheme}:" scheme. The host accepts same-origin http(s) URLs only and ${refusal}.`,
34072
+ path: pathOf(index, field)
33968
34073
  };
33969
34074
  }
33970
34075
  if (scheme) {
33971
34076
  return {
33972
34077
  level: "warning",
33973
34078
  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)
34079
+ 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.`,
34080
+ path: pathOf(index, field)
33976
34081
  };
33977
34082
  }
33978
34083
  return void 0;
@@ -33983,8 +34088,8 @@ function validateCapabilities2(value, index, known) {
33983
34088
  {
33984
34089
  level: "warning",
33985
34090
  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")
34091
+ message: `${pathOf(index)} declares no capabilities. Accepting the install dialog grants exactly what is declared, so the plugin will be denied everything at runtime.`,
34092
+ path: pathOf(index, "capabilities")
33988
34093
  }
33989
34094
  ];
33990
34095
  }
@@ -33993,8 +34098,8 @@ function validateCapabilities2(value, index, known) {
33993
34098
  {
33994
34099
  level: "error",
33995
34100
  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")
34101
+ message: `${pathOf(index, "capabilities")} must be an array; the host ignores any other shape and grants nothing.`,
34102
+ path: pathOf(index, "capabilities")
33998
34103
  }
33999
34104
  ];
34000
34105
  }
@@ -34004,8 +34109,8 @@ function validateCapabilities2(value, index, known) {
34004
34109
  findings.push({
34005
34110
  level: "error",
34006
34111
  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")
34112
+ 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(", ")}.`,
34113
+ path: pathOf(index, "capabilities")
34009
34114
  });
34010
34115
  }
34011
34116
  }
@@ -34017,58 +34122,69 @@ function validateEntry(raw, index, known) {
34017
34122
  {
34018
34123
  level: "error",
34019
34124
  code: "catalog.entry",
34020
- message: `${at(index)} is not an object, so the host drops it.`,
34021
- path: at(index)
34125
+ message: `${pathOf(index)} is not an object, so the host drops it.`,
34126
+ path: pathOf(index)
34022
34127
  }
34023
34128
  ];
34024
34129
  }
34025
34130
  return [
34026
- ...validateEntryIdentity(raw, index),
34131
+ ...validateId(raw, index),
34132
+ ...validateSameOriginUrls(raw, index),
34133
+ ...validateNameAndVersion(raw, index),
34027
34134
  ...validateCapabilities2(raw["capabilities"], index, known),
34028
34135
  ...validateEntryMetadata(raw, index),
34029
34136
  ...validateEntryKeys(raw, index)
34030
34137
  ];
34031
34138
  }
34032
- function validateEntryIdentity(raw, index) {
34033
- const findings = [];
34034
- if (typeof raw["id"] !== "string" || raw["id"].length === 0) {
34035
- findings.push({
34139
+ function validateId(raw, index) {
34140
+ return typeof raw["id"] === "string" && raw["id"].length > 0 ? [] : [
34141
+ {
34036
34142
  level: "error",
34037
34143
  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;
34144
+ message: `${pathOf(index, "id")} must be a non-empty string; the host drops the whole entry without it.`,
34145
+ path: pathOf(index, "id")
34045
34146
  }
34046
- const finding = urlFinding(raw[field], index, field, field === "entryUrl");
34147
+ ];
34148
+ }
34149
+ function validateSameOriginUrls(raw, index) {
34150
+ const findings = [];
34151
+ for (const field of SAME_ORIGIN_FIELDS) {
34152
+ const finding = sameOriginUrlFinding(raw, index, field);
34047
34153
  if (finding) {
34048
34154
  findings.push(finding);
34049
34155
  }
34050
34156
  }
34157
+ return findings;
34158
+ }
34159
+ function sameOriginUrlFinding(raw, index, field) {
34160
+ if (field === "entryUrl") {
34161
+ return requiredUrlFinding(raw[field], index, field);
34162
+ }
34163
+ return raw[field] === void 0 ? void 0 : optionalUrlFinding(raw[field], index, field);
34164
+ }
34165
+ function validateNameAndVersion(raw, index) {
34166
+ const findings = [];
34051
34167
  if (raw["name"] === void 0) {
34052
34168
  findings.push({
34053
34169
  level: "warning",
34054
34170
  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")
34171
+ message: `${pathOf(index)} has no name, so the store falls back to showing the id.`,
34172
+ path: pathOf(index, "name")
34057
34173
  });
34058
34174
  } else if (typeof raw["name"] !== "string" || raw["name"].length === 0) {
34059
34175
  findings.push({
34060
34176
  level: "warning",
34061
34177
  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")
34178
+ message: `${pathOf(index, "name")} is not a non-empty string, so the store falls back to showing the id.`,
34179
+ path: pathOf(index, "name")
34064
34180
  });
34065
34181
  }
34066
34182
  if (raw["version"] === void 0) {
34067
34183
  findings.push({
34068
34184
  level: "warning",
34069
34185
  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")
34186
+ 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.`,
34187
+ path: pathOf(index, "version")
34072
34188
  });
34073
34189
  }
34074
34190
  return findings;
@@ -34079,24 +34195,24 @@ function validateEntryMetadata(raw, index) {
34079
34195
  findings.push({
34080
34196
  level: "warning",
34081
34197
  code: "catalog.downloads",
34082
- message: `${at(index, "downloads")} must be a non-negative number, so the host ignores it.`,
34083
- path: at(index, "downloads")
34198
+ message: `${pathOf(index, "downloads")} must be a non-negative number, so the host ignores it.`,
34199
+ path: pathOf(index, "downloads")
34084
34200
  });
34085
34201
  }
34086
34202
  if (raw["updated"] !== void 0 && !isRenderableDate(raw["updated"])) {
34087
34203
  findings.push({
34088
34204
  level: "warning",
34089
34205
  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")
34206
+ message: `${pathOf(index, "updated")} is not a date the store can parse, so it renders the raw string instead of "2 days ago".`,
34207
+ path: pathOf(index, "updated")
34092
34208
  });
34093
34209
  }
34094
34210
  if (raw["repository"] !== void 0 && !isHttpUrl(raw["repository"])) {
34095
34211
  findings.push({
34096
34212
  level: "warning",
34097
34213
  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")
34214
+ message: `${pathOf(index, "repository")} must be an http(s) URL, so the host drops it and the detail pane shows no link.`,
34215
+ path: pathOf(index, "repository")
34100
34216
  });
34101
34217
  }
34102
34218
  return findings;
@@ -34108,8 +34224,8 @@ function validateEntryKeys(raw, index) {
34108
34224
  findings.push({
34109
34225
  level: "warning",
34110
34226
  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)
34227
+ 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.`,
34228
+ path: pathOf(index, key)
34113
34229
  });
34114
34230
  }
34115
34231
  }
@@ -34150,8 +34266,8 @@ function validateCatalog(catalog, known = KNOWN_CAPABILITIES) {
34150
34266
  findings.push({
34151
34267
  level: "warning",
34152
34268
  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")
34269
+ message: `${pathOf(index, "id")} repeats "${id}". The host keeps the first entry with an id and drops the rest.`,
34270
+ path: pathOf(index, "id")
34155
34271
  });
34156
34272
  }
34157
34273
  seen.add(id);
@@ -34167,7 +34283,7 @@ function validateCommands(sources, ts) {
34167
34283
  for (const source of sources) {
34168
34284
  const file2 = ts.createSourceFile(source.path, source.text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
34169
34285
  for (const call of registerCommandCalls(ts, file2)) {
34170
- findings.push(...judge(read(ts, file2, call)));
34286
+ findings.push(...findingsFor(readRegistration(ts, file2, call)));
34171
34287
  }
34172
34288
  }
34173
34289
  findings.push({ level: "info", code: "commands.runtime", message: RUNTIME_NOTE });
@@ -34184,11 +34300,11 @@ function registerCommandCalls(ts, file2) {
34184
34300
  visit(file2);
34185
34301
  return calls;
34186
34302
  }
34187
- function read(ts, file2, call) {
34303
+ function readRegistration(ts, file2, call) {
34188
34304
  const line = file2.getLineAndCharacterOfPosition(call.getStart(file2)).line + 1;
34189
- const at2 = `${file2.fileName}:${line}`;
34305
+ const at = `${file2.fileName}:${line}`;
34190
34306
  const literal2 = call.arguments[0];
34191
- const empty = { at: at2, callable: false, described: false, answers: false, returnsValue: false, arguments: [] };
34307
+ const empty = { at, callable: false, described: false, answers: false, returnsValue: false, arguments: [] };
34192
34308
  if (!literal2 || !ts.isObjectLiteralExpression(literal2)) {
34193
34309
  return { ...empty, unreadable: "its argument is not an object literal" };
34194
34310
  }
@@ -34210,7 +34326,7 @@ function read(ts, file2, call) {
34210
34326
  const callable = properties.get("callable");
34211
34327
  const consent = properties.get("agentConsent");
34212
34328
  return {
34213
- at: at2,
34329
+ at,
34214
34330
  id: id.text,
34215
34331
  callable: callable?.kind === ts.SyntaxKind.TrueKeyword,
34216
34332
  described: properties.has("description"),
@@ -34230,25 +34346,27 @@ function unwrapped(ts, value) {
34230
34346
  }
34231
34347
  return ts.isAsExpression(value) || ts.isSatisfiesExpression(value) || ts.isParenthesizedExpression(value) ? unwrapped(ts, value.expression) : value;
34232
34348
  }
34233
- var STATED = ["allow", "ask", "ask-always", "never"];
34349
+ var CONSENT_LINES = /* @__PURE__ */ new Map([
34350
+ ["allow", "says an agent's word is enough to run it."],
34351
+ [
34352
+ "ask",
34353
+ "says the person is asked first; whoever runs it on an agent's behalf does the asking."
34354
+ ],
34355
+ [
34356
+ "ask-always",
34357
+ "says the person is asked every time; whoever runs it on an agent's behalf does the asking."
34358
+ ],
34359
+ [
34360
+ "never",
34361
+ "says it is not to be run on an agent's word; closing it to other callers is what enforces that."
34362
+ ]
34363
+ ]);
34364
+ var STATED_CONSENTS = [...CONSENT_LINES.keys()];
34234
34365
  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
- }
34366
+ if (consent === void 0) {
34367
+ return "says nothing about whether an agent's word is enough to run it.";
34251
34368
  }
34369
+ return CONSENT_LINES.get(consent) ?? `declares "${consent}" about an agent's word, which is none of ${STATED_CONSENTS.join(", ")}, so nothing reads it.`;
34252
34370
  }
34253
34371
  function readArguments(ts, value) {
34254
34372
  if (!value || !ts.isArrayLiteralExpression(value)) {
@@ -34295,15 +34413,15 @@ function returnsValue(ts, run) {
34295
34413
  visit(run);
34296
34414
  return found;
34297
34415
  }
34298
- function judge(registration) {
34299
- const { at: at2, id } = registration;
34416
+ function findingsFor(registration) {
34417
+ const { at, id } = registration;
34300
34418
  if (registration.unreadable) {
34301
34419
  return [
34302
34420
  {
34303
34421
  level: "info",
34304
34422
  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.`
34423
+ path: at,
34424
+ message: `A registerCommand call at ${at} could not be read: ${registration.unreadable}. What it offers an agent is unknown here.`
34307
34425
  }
34308
34426
  ];
34309
34427
  }
@@ -34312,7 +34430,7 @@ function judge(registration) {
34312
34430
  {
34313
34431
  level: "info",
34314
34432
  code: "command.private",
34315
- path: at2,
34433
+ path: at,
34316
34434
  message: `${id}: not offered to an agent, because it is not callable; an agent never sees it.`
34317
34435
  }
34318
34436
  ];
@@ -34322,7 +34440,7 @@ function judge(registration) {
34322
34440
  findings.push({
34323
34441
  level: "warning",
34324
34442
  code: "command.description",
34325
- path: at2,
34443
+ path: at,
34326
34444
  message: `${id}: offered to an agent without a description; the agent sees only the id and has to guess what it does.`
34327
34445
  });
34328
34446
  }
@@ -34331,7 +34449,7 @@ function judge(registration) {
34331
34449
  findings.push({
34332
34450
  level: "info",
34333
34451
  code: "command.argument",
34334
- path: at2,
34452
+ path: at,
34335
34453
  message: `${id}: argument "${argument.name}" has no description; an agent sees a bare name and guesses its value.`
34336
34454
  });
34337
34455
  }
@@ -34340,7 +34458,7 @@ function judge(registration) {
34340
34458
  findings.push({
34341
34459
  level: "info",
34342
34460
  code: "command.answers",
34343
- path: at2,
34461
+ path: at,
34344
34462
  message: `${id}: run returns a value but declares no answers; an agent hears that the command ran, not what it answered.`
34345
34463
  });
34346
34464
  }
@@ -34349,14 +34467,14 @@ function judge(registration) {
34349
34467
  findings.push({
34350
34468
  level: "info",
34351
34469
  code: "command.offered",
34352
- path: at2,
34470
+ path: at,
34353
34471
  message: `${id}: offered to an agent, with ${count} described argument${count === 1 ? "" : "s"}${registration.answers ? " and a declared answer" : ""}.`
34354
34472
  });
34355
34473
  }
34356
34474
  findings.push({
34357
34475
  level: "info",
34358
34476
  code: "command.consent",
34359
- path: at2,
34477
+ path: at,
34360
34478
  message: `${id}: ${consentLine(registration.agentConsent)}`
34361
34479
  });
34362
34480
  return findings;
@@ -34394,7 +34512,7 @@ function listGenerators() {
34394
34512
  }))
34395
34513
  });
34396
34514
  }
34397
- function valuesFor(scaffold2, args) {
34515
+ function portableValuesFrom(scaffold2, args) {
34398
34516
  const values = {};
34399
34517
  for (const option of portableOptions(scaffold2)) {
34400
34518
  const value = args[option.name];
@@ -34409,31 +34527,31 @@ function valuesFor(scaffold2, args) {
34409
34527
  }
34410
34528
  var WHERE_THE_FILES_LAND = "<the directory you wrote these files into>";
34411
34529
  function scaffold(scaffold2, args) {
34412
- const values = valuesFor(scaffold2, args);
34530
+ const values = portableValuesFrom(scaffold2, args);
34413
34531
  const remaining = (scaffold2.amend?.({ ...values, directory: WHERE_THE_FILES_LAND }) ?? []).map((amendment) => describeAmendment(amendment));
34414
34532
  return ok({
34415
34533
  files: scaffold2.build(values),
34416
34534
  ...remaining.length > 0 && { remaining }
34417
34535
  });
34418
34536
  }
34419
- function validateManifestTool(a) {
34537
+ function validateManifestTool(args) {
34420
34538
  return ok({
34421
34539
  findings: validateManifest({
34422
- id: a["id"],
34423
- name: a["name"],
34424
- capabilities: a["capabilities"]
34540
+ id: args["id"],
34541
+ name: args["name"],
34542
+ capabilities: args["capabilities"]
34425
34543
  })
34426
34544
  });
34427
34545
  }
34428
- function validateCatalogTool(a) {
34429
- return ok({ findings: validateCatalog(a["catalog"]) });
34546
+ function validateCatalogTool(args) {
34547
+ return ok({ findings: validateCatalog(args["catalog"]) });
34430
34548
  }
34431
- function validateI18nTool(a) {
34432
- const bundles = a["bundles"] ?? {};
34549
+ function validateI18nTool(args) {
34550
+ const bundles = args["bundles"] ?? {};
34433
34551
  return ok({ findings: validateI18nParity(bundles) });
34434
34552
  }
34435
- function validateCommandsTool(a) {
34436
- const files = a["files"] ?? {};
34553
+ function validateCommandsTool(args) {
34554
+ const files = args["files"] ?? {};
34437
34555
  const ts = loadTypeScript(process.cwd());
34438
34556
  if (!ts) {
34439
34557
  return ok({
@@ -34470,7 +34588,7 @@ function inputSchema(descriptor) {
34470
34588
  function createMcpServer() {
34471
34589
  const server = new McpServer({
34472
34590
  name: "loomweaver-devkit",
34473
- version: "0.14.1"
34591
+ version: "0.14.3"
34474
34592
  });
34475
34593
  server.registerTool(
34476
34594
  "list_generators",