@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.219

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 (177) hide show
  1. package/README.md +2 -2
  2. package/package.json +17 -17
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js.map +1 -1
  5. package/src/lib/activity/ai-activity-actions.js +8 -1
  6. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  7. package/src/lib/components/ai-assist-provider.component.js +2 -2
  8. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  9. package/src/lib/components/ai-brief-dialog.component.js +3 -3
  10. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  11. package/src/lib/components/ai-experiment-cards.component.js +1 -1
  12. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  13. package/src/lib/components/ai-job-plan.component.d.ts +8 -0
  14. package/src/lib/components/ai-job-plan.component.js +21 -4
  15. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  16. package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
  17. package/src/lib/components/ai-permissions-on-host.component.js +12 -3
  18. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  19. package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
  20. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  21. package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
  22. package/src/lib/components/ai-product-import-option.component.js.map +1 -1
  23. package/src/lib/components/ai-product-zones.d.ts +124 -0
  24. package/src/lib/components/ai-product-zones.js +32 -0
  25. package/src/lib/components/ai-product-zones.js.map +1 -0
  26. package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
  27. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  28. package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
  29. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  30. package/src/lib/components/ai-seo-audit-card.component.js +35 -92
  31. package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
  32. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  33. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  34. package/src/lib/components/assist-jobs-drawer.component.js +21 -15
  35. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  36. package/src/lib/components/assist-panel.component.js +2 -2
  37. package/src/lib/components/assist-panel.component.js.map +1 -1
  38. package/src/lib/components/assist-signals-page.component.js +2 -2
  39. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  40. package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
  41. package/src/lib/jobs/ai-email-bindings.js +75 -42
  42. package/src/lib/jobs/ai-email-bindings.js.map +1 -1
  43. package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
  44. package/src/lib/jobs/ai-job-campaign-step.js +5 -10
  45. package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
  46. package/src/lib/jobs/ai-job-component-step.js +9 -3
  47. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  48. package/src/lib/jobs/ai-job-drafts.js +1 -1
  49. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  50. package/src/lib/jobs/ai-job-email-step.d.ts +0 -2
  51. package/src/lib/jobs/ai-job-email-step.js +1 -3
  52. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  53. package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
  54. package/src/lib/jobs/ai-job-experiment-step.js +1 -1
  55. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  56. package/src/lib/jobs/ai-job-generation.d.ts +1 -1
  57. package/src/lib/jobs/ai-job-generation.js +10 -2
  58. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  59. package/src/lib/jobs/ai-job-layout-step.js +1 -1
  60. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  61. package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
  62. package/src/lib/jobs/ai-job-page-sections.js +7 -4
  63. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  64. package/src/lib/jobs/ai-job-page-step.js +7 -2
  65. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  66. package/src/lib/jobs/ai-job-plan-conformance.js +33 -0
  67. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +3 -0
  69. package/src/lib/jobs/ai-job-plan-step.js +29 -17
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
  72. package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
  73. package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
  74. package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
  75. package/src/lib/jobs/ai-job-products-step.js +54 -32
  76. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  77. package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
  78. package/src/lib/jobs/ai-job-seo-step.js +35 -108
  79. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  80. package/src/lib/jobs/ai-job-template-step.js +1 -0
  81. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
  83. package/src/lib/jobs/ai-job-theme-step.js +1 -1
  84. package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
  85. package/src/lib/jobs/ai-job-workflow-step.js +17 -9
  86. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  87. package/src/lib/jobs/ai-workflow-records.d.ts +19 -11
  88. package/src/lib/jobs/ai-workflow-records.js +60 -27
  89. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  90. package/src/lib/model/ai-allotments.d.ts +11 -3
  91. package/src/lib/model/ai-allotments.js +21 -4
  92. package/src/lib/model/ai-allotments.js.map +1 -1
  93. package/src/lib/model/ai-automation-outline.d.ts +21 -2
  94. package/src/lib/model/ai-automation-outline.js.map +1 -1
  95. package/src/lib/model/ai-build-plan.d.ts +57 -0
  96. package/src/lib/model/ai-build-plan.js +116 -7
  97. package/src/lib/model/ai-build-plan.js.map +1 -1
  98. package/src/lib/model/ai-seo.d.ts +11 -51
  99. package/src/lib/model/ai-seo.js +0 -17
  100. package/src/lib/model/ai-seo.js.map +1 -1
  101. package/src/lib/model/ai-site-inventory.js +1 -1
  102. package/src/lib/model/ai-site-inventory.js.map +1 -1
  103. package/src/lib/model/ai-template-subjects.js +2 -2
  104. package/src/lib/model/ai-template-subjects.js.map +1 -1
  105. package/src/lib/model/ai-theme-proposal.d.ts +1 -1
  106. package/src/lib/model/ai-theme-proposal.js +1 -1
  107. package/src/lib/model/ai-theme-proposal.js.map +1 -1
  108. package/src/lib/model/ai-workflow-job.d.ts +1 -3
  109. package/src/lib/model/ai-workflow-job.js +1 -2
  110. package/src/lib/model/ai-workflow-job.js.map +1 -1
  111. package/src/lib/plan-entitlements.d.ts +149 -0
  112. package/src/lib/plan-entitlements.js +168 -0
  113. package/src/lib/plan-entitlements.js.map +1 -0
  114. package/src/lib/plugin.js +1 -1
  115. package/src/lib/plugin.js.map +1 -1
  116. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
  117. package/src/lib/runtime/ai-doctrine-validators.js +178 -21
  118. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  119. package/src/lib/runtime/ai-doctrine.js +5 -5
  120. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  121. package/src/lib/runtime/ai-eval-live.js +29 -14
  122. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  123. package/src/lib/runtime/ai-eval.d.ts +8 -1
  124. package/src/lib/runtime/ai-eval.js +29 -2
  125. package/src/lib/runtime/ai-eval.js.map +1 -1
  126. package/src/lib/runtime/ai-node-tree.js +3 -3
  127. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  128. package/src/lib/runtime/ai-palette.d.ts +6 -0
  129. package/src/lib/runtime/ai-palette.generated.js +15 -12
  130. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  131. package/src/lib/runtime/ai-palette.js +7 -1
  132. package/src/lib/runtime/ai-palette.js.map +1 -1
  133. package/src/lib/runtime/seo-audit.d.ts +13 -63
  134. package/src/lib/runtime/seo-audit.js +22 -207
  135. package/src/lib/runtime/seo-audit.js.map +1 -1
  136. package/src/lib/runtime/site-inventory.js +1 -1
  137. package/src/lib/runtime/site-inventory.js.map +1 -1
  138. package/src/lib/server/ai-assist-prompts.js +3 -3
  139. package/src/lib/server/ai-assist-prompts.js.map +1 -1
  140. package/src/lib/server/assist-chat.js +11 -10
  141. package/src/lib/server/assist-chat.js.map +1 -1
  142. package/src/lib/server/assist-view-context.js +23 -23
  143. package/src/lib/server/assist-view-context.js.map +1 -1
  144. package/src/lib/server.d.ts +1 -0
  145. package/src/lib/server.js.map +1 -1
  146. package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
  147. package/src/lib/testing/stand-in-automation-index.js +102 -0
  148. package/src/lib/testing/stand-in-automation-index.js.map +1 -0
  149. package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
  150. package/src/lib/testing/stand-in-list-send-time.js +77 -0
  151. package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
  152. package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
  153. package/src/lib/testing/stand-in-pipeline-index.js +59 -0
  154. package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
  155. package/src/lib/testing/stand-in-product-index.d.ts +20 -0
  156. package/src/lib/testing/stand-in-product-index.js +105 -0
  157. package/src/lib/testing/stand-in-product-index.js.map +1 -0
  158. package/src/lib/tools/ai-component-tool.js +2 -2
  159. package/src/lib/tools/ai-component-tool.js.map +1 -1
  160. package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
  161. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  162. package/src/lib/tools/ai-seo-tool.d.ts +6 -18
  163. package/src/lib/tools/ai-seo-tool.js +7 -38
  164. package/src/lib/tools/ai-seo-tool.js.map +1 -1
  165. package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
  166. package/src/lib/tools/ai-workflow-tool.js +1 -1
  167. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  168. package/src/lib/usage/ai-allotment-alerts.js +8 -1
  169. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  170. package/src/lib/usage/assist-free-taste.js +2 -1
  171. package/src/lib/usage/assist-free-taste.js.map +1 -1
  172. package/src/lib/usage-axes.d.ts +39 -0
  173. package/src/lib/usage-axes.js +70 -0
  174. package/src/lib/usage-axes.js.map +1 -0
  175. package/src/lib/runtime/seo-page-facts.d.ts +0 -61
  176. package/src/lib/runtime/seo-page-facts.js +0 -150
  177. package/src/lib/runtime/seo-page-facts.js.map +0 -1
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { ASSIST_CREDIT_COST_USD, ASSIST_PROVIDER_COST_FIELD } from "@aglyn/aglyn/app-utils/assist-credits";
17
+ /**
18
+ * THE AI PLUGIN'S METER (AGL-3080): provider spend, in the platform's cost
19
+ * model and against the credit band in its utilization table. Compiled into
20
+ * core by the manifest generator (`register.usageAxes`).
21
+ *
22
+ * The rollup records `assistCostUsd` — what the month's tokens cost US, at
23
+ * the provider's rates (AGL-3015) — so the axis enters the cost model at ×1
24
+ * and names no rate. It is the one line on the platform whose unit cost is
25
+ * real money paid to a third party, and the only one that can clear the
26
+ * $2/site floor on its own, which is why a margin surface that could not see
27
+ * it would rate a token-heavy workspace exactly as it rates an idle one.
28
+ *
29
+ * LIVE, because the rollup is a snapshot from when the cron ran and assist
30
+ * keeps spending after it: a reader that fetches the month's
31
+ * `assistUsage/{month}` document takes the provider figure, or the billed
32
+ * one on a month closed before the two were split, over the snapshot.
33
+ *
34
+ * The band is sold in credits, and a credit is `ASSIST_CREDIT_COST_USD` of
35
+ * spend, so what was used is the dollars over that — rounded up, the one
36
+ * conversion the customer's own meter uses.
37
+ */ export function aiUsageAxes() {
38
+ return {
39
+ costAxes: [
40
+ {
41
+ id: 'assist',
42
+ order: 80,
43
+ fields: [
44
+ 'assistCostUsd'
45
+ ],
46
+ live: {
47
+ collection: 'assistUsage',
48
+ fields: [
49
+ ASSIST_PROVIDER_COST_FIELD,
50
+ 'estCostUsd'
51
+ ]
52
+ }
53
+ }
54
+ ],
55
+ bands: [
56
+ {
57
+ id: 'assistCredits',
58
+ label: 'Assist credits',
59
+ order: 90,
60
+ fields: [
61
+ 'assistCostUsd'
62
+ ],
63
+ entitlement: 'assistCreditsPerMonth',
64
+ unitCostUsd: ASSIST_CREDIT_COST_USD
65
+ }
66
+ ]
67
+ };
68
+ }
69
+
70
+ //# sourceMappingURL=usage-axes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/usage-axes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginUsageAxesDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-usage-axes'\nimport {\n ASSIST_CREDIT_COST_USD,\n ASSIST_PROVIDER_COST_FIELD,\n} from '@aglyn/aglyn/app-utils/assist-credits'\n\n/**\n * THE AI PLUGIN'S METER (AGL-3080): provider spend, in the platform's cost\n * model and against the credit band in its utilization table. Compiled into\n * core by the manifest generator (`register.usageAxes`).\n *\n * The rollup records `assistCostUsd` — what the month's tokens cost US, at\n * the provider's rates (AGL-3015) — so the axis enters the cost model at ×1\n * and names no rate. It is the one line on the platform whose unit cost is\n * real money paid to a third party, and the only one that can clear the\n * $2/site floor on its own, which is why a margin surface that could not see\n * it would rate a token-heavy workspace exactly as it rates an idle one.\n *\n * LIVE, because the rollup is a snapshot from when the cron ran and assist\n * keeps spending after it: a reader that fetches the month's\n * `assistUsage/{month}` document takes the provider figure, or the billed\n * one on a month closed before the two were split, over the snapshot.\n *\n * The band is sold in credits, and a credit is `ASSIST_CREDIT_COST_USD` of\n * spend, so what was used is the dollars over that — rounded up, the one\n * conversion the customer's own meter uses.\n */\nexport function aiUsageAxes(): PluginUsageAxesDeclaration {\n return {\n costAxes: [\n {\n id: 'assist',\n order: 80,\n fields: ['assistCostUsd'],\n live: {\n collection: 'assistUsage',\n fields: [ASSIST_PROVIDER_COST_FIELD, 'estCostUsd'],\n },\n },\n ],\n bands: [\n {\n id: 'assistCredits',\n label: 'Assist credits',\n order: 90,\n fields: ['assistCostUsd'],\n entitlement: 'assistCreditsPerMonth',\n unitCostUsd: ASSIST_CREDIT_COST_USD,\n },\n ],\n }\n}\n"],"names":["ASSIST_CREDIT_COST_USD","ASSIST_PROVIDER_COST_FIELD","aiUsageAxes","costAxes","id","order","fields","live","collection","bands","label","entitlement","unitCostUsd"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,sBAAsB,EACtBC,0BAA0B,QACrB,wCAAuC;AAE9C;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,OAAO;QACLC,UAAU;YACR;gBACEC,IAAI;gBACJC,OAAO;gBACPC,QAAQ;oBAAC;iBAAgB;gBACzBC,MAAM;oBACJC,YAAY;oBACZF,QAAQ;wBAACL;wBAA4B;qBAAa;gBACpD;YACF;SACD;QACDQ,OAAO;YACL;gBACEL,IAAI;gBACJM,OAAO;gBACPL,OAAO;gBACPC,QAAQ;oBAAC;iBAAgB;gBACzBK,aAAa;gBACbC,aAAaZ;YACf;SACD;IACH;AACF"}
@@ -1,61 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- import { type PageMarkdownNodes } from '@aglyn/aglyn/app-utils/page-markdown';
18
- /**
19
- * What a page SAYS, read off its node tree (AGL-2910): the text a search
20
- * listing is written from, the headings, and the images — each heading and
21
- * image by node id, because a content fix addresses the element it changes.
22
- *
23
- * The text is the page's Markdown representation, the one an agent is
24
- * served, so what the listing summarizes is what a reader of the page gets.
25
- * The walk starts where that serializer starts — the content region — and
26
- * skips what it skips, through the same chrome predicate: a heading in the
27
- * site's navigation is not the page's heading.
28
- */
29
- /** How much of a page's text a prompt carries. A listing summarizes; it does not need the page. */
30
- export declare const AI_SEO_PAGE_TEXT_MAX_CHARS = 2000;
31
- export interface AiSeoHeading {
32
- /** The element, or `null` for a heading inside rich text a fix cannot address. */
33
- nodeId: string | null;
34
- level: number;
35
- text: string;
36
- /** Whether a content fix can change it: a text component with plain text. */
37
- editable: boolean;
38
- }
39
- export interface AiSeoImage {
40
- nodeId: string;
41
- src: string;
42
- alt: string;
43
- /** The nearest heading or text before it, which is what the image illustrates. */
44
- context: string;
45
- }
46
- export interface AiSeoPageFacts {
47
- /** The page's Markdown body, capped. */
48
- text: string;
49
- wordCount: number;
50
- headings: AiSeoHeading[];
51
- h1s: AiSeoHeading[];
52
- images: AiSeoImage[];
53
- imagesMissingAlt: AiSeoImage[];
54
- /** The node the content region starts at; a new heading goes first inside it. */
55
- contentRootId: string | null;
56
- }
57
- /** Read the facts a listing and an audit need from one page's node map. */
58
- export declare function aiSeoPageFacts(nodes: PageMarkdownNodes | null | undefined, options?: {
59
- rootId?: string;
60
- maxChars?: number;
61
- }): AiSeoPageFacts;
@@ -1,150 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */ import { buildPageMarkdown, headingLevelOf, isPageChromeNode, pageContentRootId } from "@aglyn/aglyn/app-utils/page-markdown";
17
- /**
18
- * What a page SAYS, read off its node tree (AGL-2910): the text a search
19
- * listing is written from, the headings, and the images — each heading and
20
- * image by node id, because a content fix addresses the element it changes.
21
- *
22
- * The text is the page's Markdown representation, the one an agent is
23
- * served, so what the listing summarizes is what a reader of the page gets.
24
- * The walk starts where that serializer starts — the content region — and
25
- * skips what it skips, through the same chrome predicate: a heading in the
26
- * site's navigation is not the page's heading.
27
- */ /** How much of a page's text a prompt carries. A listing summarizes; it does not need the page. */ export const AI_SEO_PAGE_TEXT_MAX_CHARS = 2000;
28
- /** How much text around an image describes it. */ const IMAGE_CONTEXT_MAX_CHARS = 160;
29
- /** The deepest a walk goes, and it stops at a cycle — a node map is data. */ const MAX_DEPTH = 64;
30
- /** The text components whose heading level `headingLevelOf` reads. */ const TEXT_COMPONENTS = new Set([
31
- 'muiTypography',
32
- 'muiInlineText',
33
- 'muiListItemText'
34
- ]);
35
- const collapse = (value)=>value.replace(/\s+/g, ' ').trim();
36
- /** Author HTML to its text, for a heading stored as rich text. */ function htmlText(html) {
37
- return collapse(html.replace(/<[^>]*>/g, ' ').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&'));
38
- }
39
- /** A text component's own text, and whether it is plain (a fix can rewrite it). */ function textOf(props) {
40
- const html = props['html'];
41
- if (typeof html === 'string' && html.trim()) return {
42
- text: htmlText(html),
43
- plain: false
44
- };
45
- const children = props['children'];
46
- return typeof children === 'string' ? {
47
- text: collapse(children),
48
- plain: true
49
- } : {
50
- text: '',
51
- plain: true
52
- };
53
- }
54
- const childIdsOf = (node)=>Array.isArray(node == null ? void 0 : node.nodes) ? (node == null ? void 0 : node.nodes).filter((id)=>typeof id === 'string') : [];
55
- /** Read the facts a listing and an audit need from one page's node map. */ export function aiSeoPageFacts(nodes, options = {}) {
56
- var _options_maxChars;
57
- const empty = {
58
- text: '',
59
- wordCount: 0,
60
- headings: [],
61
- h1s: [],
62
- images: [],
63
- imagesMissingAlt: [],
64
- contentRootId: null
65
- };
66
- if (!nodes) return empty;
67
- const contentRootId = pageContentRootId(nodes, options.rootId);
68
- if (!contentRootId) return empty;
69
- const markdown = buildPageMarkdown({
70
- nodes,
71
- rootId: options.rootId
72
- });
73
- const text = markdown.trim().slice(0, (_options_maxChars = options.maxChars) != null ? _options_maxChars : AI_SEO_PAGE_TEXT_MAX_CHARS);
74
- const headings = [];
75
- const images = [];
76
- let lastText = '';
77
- const seen = new Set();
78
- const visit = (id, depth)=>{
79
- var _node_props, _node_componentId, _props_html;
80
- if (depth > MAX_DEPTH || seen.has(id)) return;
81
- const node = nodes[id];
82
- if (!node) return;
83
- seen.add(id);
84
- if (isPageChromeNode(node)) return;
85
- const props = (_node_props = node.props) != null ? _node_props : {};
86
- const componentId = String((_node_componentId = node.componentId) != null ? _node_componentId : '');
87
- if (TEXT_COMPONENTS.has(componentId)) {
88
- const { text: own, plain } = textOf(props);
89
- const level = headingLevelOf(props);
90
- if (own) {
91
- if (level > 0) headings.push({
92
- nodeId: id,
93
- level,
94
- text: own,
95
- editable: plain
96
- });
97
- lastText = own;
98
- }
99
- } else if (componentId === 'markdown' && typeof props['content'] === 'string') {
100
- for (const line of String(props['content']).split('\n')){
101
- const match = /^(#{1,6})\s+(.+)$/.exec(line.trim());
102
- if (match) {
103
- headings.push({
104
- nodeId: null,
105
- level: match[1].length,
106
- text: collapse(match[2]),
107
- editable: false
108
- });
109
- }
110
- }
111
- lastText = collapse(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS);
112
- } else if (componentId === 'custom-html' && typeof ((_props_html = props['html']) != null ? _props_html : props['children']) === 'string') {
113
- var _props_html1;
114
- const html = String((_props_html1 = props['html']) != null ? _props_html1 : props['children']);
115
- for (const match of html.matchAll(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi)){
116
- headings.push({
117
- nodeId: null,
118
- level: Number(match[1]),
119
- text: htmlText(match[2]),
120
- editable: false
121
- });
122
- }
123
- } else if (componentId === 'image') {
124
- var _props_src;
125
- const src = String((_props_src = props['src']) != null ? _props_src : '').trim();
126
- if (src) {
127
- var _props_alt;
128
- images.push({
129
- nodeId: id,
130
- src,
131
- alt: collapse(String((_props_alt = props['alt']) != null ? _props_alt : '')),
132
- context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS)
133
- });
134
- }
135
- }
136
- for (const childId of childIdsOf(node))visit(childId, depth + 1);
137
- };
138
- visit(contentRootId, 0);
139
- return {
140
- text,
141
- wordCount: text ? text.split(/\s+/).filter(Boolean).length : 0,
142
- headings,
143
- h1s: headings.filter((heading)=>heading.level === 1),
144
- images,
145
- imagesMissingAlt: images.filter((image)=>!image.alt),
146
- contentRootId
147
- };
148
- }
149
-
150
- //# sourceMappingURL=seo-page-facts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/seo-page-facts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n buildPageMarkdown,\n headingLevelOf,\n isPageChromeNode,\n pageContentRootId,\n type PageMarkdownNode,\n type PageMarkdownNodes,\n} from '@aglyn/aglyn/app-utils/page-markdown'\n\n/**\n * What a page SAYS, read off its node tree (AGL-2910): the text a search\n * listing is written from, the headings, and the images — each heading and\n * image by node id, because a content fix addresses the element it changes.\n *\n * The text is the page's Markdown representation, the one an agent is\n * served, so what the listing summarizes is what a reader of the page gets.\n * The walk starts where that serializer starts — the content region — and\n * skips what it skips, through the same chrome predicate: a heading in the\n * site's navigation is not the page's heading.\n */\n\n/** How much of a page's text a prompt carries. A listing summarizes; it does not need the page. */\nexport const AI_SEO_PAGE_TEXT_MAX_CHARS = 2_000\n\n/** How much text around an image describes it. */\nconst IMAGE_CONTEXT_MAX_CHARS = 160\n\n/** The deepest a walk goes, and it stops at a cycle — a node map is data. */\nconst MAX_DEPTH = 64\n\n/** The text components whose heading level `headingLevelOf` reads. */\nconst TEXT_COMPONENTS: ReadonlySet<string> = new Set(['muiTypography', 'muiInlineText', 'muiListItemText'])\n\nexport interface AiSeoHeading {\n /** The element, or `null` for a heading inside rich text a fix cannot address. */\n nodeId: string | null\n level: number\n text: string\n /** Whether a content fix can change it: a text component with plain text. */\n editable: boolean\n}\n\nexport interface AiSeoImage {\n nodeId: string\n src: string\n alt: string\n /** The nearest heading or text before it, which is what the image illustrates. */\n context: string\n}\n\nexport interface AiSeoPageFacts {\n /** The page's Markdown body, capped. */\n text: string\n wordCount: number\n headings: AiSeoHeading[]\n h1s: AiSeoHeading[]\n images: AiSeoImage[]\n imagesMissingAlt: AiSeoImage[]\n /** The node the content region starts at; a new heading goes first inside it. */\n contentRootId: string | null\n}\n\nconst collapse = (value: string): string => value.replace(/\\s+/g, ' ').trim()\n\n/** Author HTML to its text, for a heading stored as rich text. */\nfunction htmlText(html: string): string {\n return collapse(html.replace(/<[^>]*>/g, ' ').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&'))\n}\n\n/** A text component's own text, and whether it is plain (a fix can rewrite it). */\nfunction textOf(props: Record<string, unknown>): { text: string; plain: boolean } {\n const html = props['html']\n if (typeof html === 'string' && html.trim()) return { text: htmlText(html), plain: false }\n const children = props['children']\n return typeof children === 'string' ? { text: collapse(children), plain: true } : { text: '', plain: true }\n}\n\nconst childIdsOf = (node: PageMarkdownNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\n/** Read the facts a listing and an audit need from one page's node map. */\nexport function aiSeoPageFacts(\n nodes: PageMarkdownNodes | null | undefined,\n options: { rootId?: string; maxChars?: number } = {},\n): AiSeoPageFacts {\n const empty: AiSeoPageFacts = {\n text: '',\n wordCount: 0,\n headings: [],\n h1s: [],\n images: [],\n imagesMissingAlt: [],\n contentRootId: null,\n }\n if (!nodes) return empty\n const contentRootId = pageContentRootId(nodes, options.rootId)\n if (!contentRootId) return empty\n\n const markdown = buildPageMarkdown({ nodes, rootId: options.rootId })\n const text = markdown.trim().slice(0, options.maxChars ?? AI_SEO_PAGE_TEXT_MAX_CHARS)\n const headings: AiSeoHeading[] = []\n const images: AiSeoImage[] = []\n let lastText = ''\n\n const seen = new Set<string>()\n const visit = (id: string, depth: number): void => {\n if (depth > MAX_DEPTH || seen.has(id)) return\n const node = nodes[id]\n if (!node) return\n seen.add(id)\n if (isPageChromeNode(node)) return\n const props = (node.props ?? {}) as Record<string, unknown>\n const componentId = String(node.componentId ?? '')\n if (TEXT_COMPONENTS.has(componentId)) {\n const { text: own, plain } = textOf(props)\n const level = headingLevelOf(props)\n if (own) {\n if (level > 0) headings.push({ nodeId: id, level, text: own, editable: plain })\n lastText = own\n }\n } else if (componentId === 'markdown' && typeof props['content'] === 'string') {\n for (const line of String(props['content']).split('\\n')) {\n const match = /^(#{1,6})\\s+(.+)$/.exec(line.trim())\n if (match) {\n headings.push({ nodeId: null, level: match[1].length, text: collapse(match[2]), editable: false })\n }\n }\n lastText = collapse(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS)\n } else if (componentId === 'custom-html' && typeof (props['html'] ?? props['children']) === 'string') {\n const html = String(props['html'] ?? props['children'])\n for (const match of html.matchAll(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi)) {\n headings.push({ nodeId: null, level: Number(match[1]), text: htmlText(match[2]), editable: false })\n }\n } else if (componentId === 'image') {\n const src = String(props['src'] ?? '').trim()\n if (src) {\n images.push({\n nodeId: id,\n src,\n alt: collapse(String(props['alt'] ?? '')),\n context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS),\n })\n }\n }\n for (const childId of childIdsOf(node)) visit(childId, depth + 1)\n }\n visit(contentRootId, 0)\n\n return {\n text,\n wordCount: text ? text.split(/\\s+/).filter(Boolean).length : 0,\n headings,\n h1s: headings.filter((heading) => heading.level === 1),\n images,\n imagesMissingAlt: images.filter((image) => !image.alt),\n contentRootId,\n }\n}\n"],"names":["buildPageMarkdown","headingLevelOf","isPageChromeNode","pageContentRootId","AI_SEO_PAGE_TEXT_MAX_CHARS","IMAGE_CONTEXT_MAX_CHARS","MAX_DEPTH","TEXT_COMPONENTS","Set","collapse","value","replace","trim","htmlText","html","textOf","props","text","plain","children","childIdsOf","node","Array","isArray","nodes","filter","id","aiSeoPageFacts","options","empty","wordCount","headings","h1s","images","imagesMissingAlt","contentRootId","rootId","markdown","slice","maxChars","lastText","seen","visit","depth","has","add","componentId","String","own","level","push","nodeId","editable","line","split","match","exec","length","matchAll","Number","src","alt","context","childId","Boolean","heading","image"],"mappings":"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"}