@jay-framework/aiditor 0.17.4 → 0.18.0

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 (48) hide show
  1. package/README.md +1 -5
  2. package/dist/actions/check-add-page-route.jay-action +12 -0
  3. package/dist/actions/check-add-page-route.jay-action.d.ts +10 -0
  4. package/dist/actions/check-page-add-page-brief.jay-action +9 -0
  5. package/dist/actions/check-page-add-page-brief.jay-action.d.ts +8 -0
  6. package/dist/actions/check-plugin-update.jay-action +14 -0
  7. package/dist/actions/check-plugin-update.jay-action.d.ts +13 -0
  8. package/dist/actions/ensure-project-plugin.jay-action +9 -0
  9. package/dist/actions/ensure-project-plugin.jay-action.d.ts +8 -0
  10. package/dist/actions/generate-add-page-brief-from-image.jay-action +20 -0
  11. package/dist/actions/generate-add-page-brief-from-image.jay-action.d.ts +20 -0
  12. package/dist/actions/get-plugin-setup-status.jay-action +9 -0
  13. package/dist/actions/get-plugin-setup-status.jay-action.d.ts +6 -0
  14. package/dist/actions/list-jay-plugins.jay-action +8 -0
  15. package/dist/actions/list-jay-plugins.jay-action.d.ts +5 -0
  16. package/dist/actions/list-plugin-contracts.jay-action +11 -0
  17. package/dist/actions/list-plugin-contracts.jay-action.d.ts +9 -0
  18. package/dist/actions/open-add-page-from-brief.jay-action +20 -0
  19. package/dist/actions/open-add-page-from-brief.jay-action.d.ts +18 -0
  20. package/dist/actions/reclassify-add-page-asset.jay-action +12 -0
  21. package/dist/actions/reclassify-add-page-asset.jay-action.d.ts +10 -0
  22. package/dist/actions/rerun-plugin-setup.jay-action +12 -0
  23. package/dist/actions/rerun-plugin-setup.jay-action.d.ts +10 -0
  24. package/dist/actions/save-add-page-draft.jay-action +10 -0
  25. package/dist/actions/save-add-page-draft.jay-action.d.ts +9 -0
  26. package/dist/actions/start-add-page-request.jay-action +12 -0
  27. package/dist/actions/start-add-page-request.jay-action.d.ts +10 -0
  28. package/dist/actions/submit-add-page.jay-action +21 -0
  29. package/dist/actions/submit-add-page.jay-action.d.ts +17 -0
  30. package/dist/actions/submit-task.jay-action +1 -0
  31. package/dist/actions/submit-task.jay-action.d.ts +1 -0
  32. package/dist/actions/sync-add-page-plugin-manifest.jay-action +11 -0
  33. package/dist/actions/sync-add-page-plugin-manifest.jay-action.d.ts +9 -0
  34. package/dist/actions/upload-add-page-asset.jay-action +13 -0
  35. package/dist/actions/upload-add-page-asset.jay-action.d.ts +13 -0
  36. package/dist/actions/write-plugin-source.jay-action +13 -0
  37. package/dist/actions/write-plugin-source.jay-action.d.ts +12 -0
  38. package/dist/add-page-agent/SKILL.md +79 -0
  39. package/dist/add-page-agent/system.md +47 -0
  40. package/dist/index.client.js +2695 -277
  41. package/dist/index.d.ts +797 -6
  42. package/dist/index.js +2682 -14
  43. package/dist/pages/aiditor/page.jay-html +603 -2
  44. package/dist/prompts/add-page-figma-brief-prompt.md +163 -0
  45. package/dist/prompts/add-page-figma-design-definitions-prompt.md +256 -0
  46. package/dist/prompts/add-page-figma-design-from-image-prompt.md +144 -0
  47. package/package.json +24 -7
  48. package/plugin.yaml +34 -0
package/dist/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { makeJayQuery, makeJayStream, makeJayStackComponent, phaseOutput, RenderPipeline } from "@jay-framework/fullstack-component";
2
2
  import { DEV_SERVER_SERVICE } from "@jay-framework/dev-server";
3
- import fs, { readFile } from "fs/promises";
3
+ import fs, { readFile, readdir } from "fs/promises";
4
4
  import path, { extname } from "path";
5
5
  import { query } from "@anthropic-ai/claude-agent-sdk";
6
6
  import fs$1 from "fs";
7
7
  import crypto from "crypto";
8
+ import { getLogger } from "@jay-framework/logger";
9
+ import { fileURLToPath } from "url";
10
+ import { spawn } from "child_process";
11
+ import { scanRoutes } from "@jay-framework/stack-route-scanner";
8
12
  import { setActionCallerOptions } from "@jay-framework/stack-client-runtime";
9
13
  const getAiditorBootstrap = makeJayQuery(
10
14
  "aiditor.getAiditorBootstrap"
@@ -225,6 +229,48 @@ async function replaceRouteSessionAfterStaleResume(projectDir, routeKey) {
225
229
  return { sessionId };
226
230
  });
227
231
  }
232
+ const AGENT_KIT_PREAMBLE = `You are working in a Jay Stack project. The current working directory is the project root.
233
+ - Read agent-kit/plugins-index.yaml for installed plugins and materialized contract paths.
234
+ - Follow Jay Stack conventions for pages, routing, and headless component binding.
235
+ - Use Read/Glob to consult agent-kit guides when needed; do not guess contract field names.`;
236
+ function loadAgentKitSystemPrompt(projectDir) {
237
+ const parts = [];
238
+ const designerPath = path.join(
239
+ projectDir,
240
+ "agent-kit",
241
+ "designer",
242
+ "INSTRUCTIONS.md"
243
+ );
244
+ const developerPath = path.join(
245
+ projectDir,
246
+ "agent-kit",
247
+ "developer",
248
+ "INSTRUCTIONS.md"
249
+ );
250
+ if (fs$1.existsSync(designerPath)) {
251
+ parts.push(fs$1.readFileSync(designerPath, "utf-8"));
252
+ }
253
+ if (fs$1.existsSync(developerPath)) {
254
+ if (parts.length > 0) parts.push("\n\n---\n\n");
255
+ parts.push(fs$1.readFileSync(developerPath, "utf-8"));
256
+ }
257
+ if (parts.length > 0) parts.push("\n\n");
258
+ parts.push(AGENT_KIT_PREAMBLE);
259
+ return parts.join("");
260
+ }
261
+ function buildChatPrompt(config, pageRoute, renderedUrl, userMessage) {
262
+ const lines = [
263
+ `Project directory: ${config.projectDir}`,
264
+ `Current page route: ${pageRoute}`,
265
+ renderedUrl ? `Rendered preview URL: ${renderedUrl}` : null,
266
+ "",
267
+ "The user is continuing a conversation about this Jay project. Answer questions and perform requested edits using agent-kit. Read contract files from agent-kit/plugins-index.yaml when binding UI.",
268
+ "",
269
+ "User message:",
270
+ userMessage
271
+ ];
272
+ return lines.filter((l) => l !== null).join("\n");
273
+ }
228
274
  function buildNonVisualPrompt(config, notes, imagePath) {
229
275
  const lines = [
230
276
  `Project directory: ${config.projectDir}`,
@@ -239,6 +285,22 @@ const ANNOTATION_TARGETING_RULES = `Annotation targeting (follow strictly):
239
285
  - The only authoritative edit target is the element indicated by the annotation marker (dot, arrow, or highlighted region). Do not choose another element because it is colorful, prominent, or a common control (e.g. buttons, links) unless the marker clearly overlaps that element.
240
286
  - If several elements could match, prefer the text or content under/near the marker (e.g. a heading) over unrelated controls elsewhere on the page.
241
287
  - If you cannot confidently map the marker to a single target in source, do not change a plausible but unmarked control. Briefly say what is ambiguous and list candidate elements or ask for clarification.`;
288
+ function structuredAnnotationBindings(ann) {
289
+ if (ann.pluginBindings?.length) return ann.pluginBindings;
290
+ if (ann.pluginBinding) return [ann.pluginBinding];
291
+ return [];
292
+ }
293
+ function appendBindingPromptLines(body, bindings, markerId, indent = "") {
294
+ if (bindings.length === 0) return;
295
+ body.push(`${indent}Headless bindings:`);
296
+ for (const b of bindings) {
297
+ const key = b.componentKey ? ` (key: ${b.componentKey})` : "";
298
+ body.push(`${indent}- Plugin: ${b.packageName} / ${b.contractName}${key}`);
299
+ body.push(
300
+ b.scope === "page" ? `${indent}- Scope: anywhere on page — add or connect this component where it best satisfies the instruction` : `${indent}- Scope: at marker — wire the element indicated by this marker to this contract's refs`
301
+ );
302
+ }
303
+ }
242
304
  function parseNotesField(notes) {
243
305
  const empty = () => ({
244
306
  structured: null,
@@ -279,7 +341,7 @@ function parseNotesField(notes) {
279
341
  plain: ""
280
342
  };
281
343
  }
282
- if (j.version === 1 && Array.isArray(j.annotations) && j.annotations.length > 0 && j.annotations.every(
344
+ if ((j.version === 1 || j.version === 1.1) && Array.isArray(j.annotations) && j.annotations.length > 0 && j.annotations.every(
283
345
  (a) => a && typeof a === "object" && typeof a.id === "string" && typeof a.instruction === "string" && typeof a.mode === "string"
284
346
  )) {
285
347
  return {
@@ -320,6 +382,7 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
320
382
  const pin = ann.id;
321
383
  const paths = attachmentPathsByAnnotationId.get(pin) ?? [];
322
384
  body.push(`Annotation ${pin} (${ann.mode})`);
385
+ appendBindingPromptLines(body, structuredAnnotationBindings(ann), ann.id);
323
386
  body.push(`Instruction: ${ann.instruction.trim()}`);
324
387
  if (paths.length > 0) {
325
388
  body.push(
@@ -392,6 +455,12 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
392
455
  const pinNum = pinById.get(ann.id) ?? "?";
393
456
  const paths = attachmentPathsByPin.get(pinNum) ?? [];
394
457
  body.push(`- Pin ${pinNum} (id ${ann.id}) — ${ann.mode}`);
458
+ appendBindingPromptLines(
459
+ body,
460
+ structuredAnnotationBindings(ann),
461
+ ann.id,
462
+ " "
463
+ );
395
464
  body.push(` Instruction: ${ann.instruction.trim()}`);
396
465
  if (ann.previewUrlAtTime) {
397
466
  body.push(
@@ -420,7 +489,7 @@ function persistFile(file, destDir, fileName) {
420
489
  fs$1.copyFileSync(file.path, dest);
421
490
  return dest;
422
491
  }
423
- const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles({ maxFileSize: 2e7, maxFiles: 300 }).withHandler(async function* (input) {
492
+ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHandler(async function* (input) {
424
493
  const projectDir = process.cwd();
425
494
  const buildFolder = path.join(projectDir, "build");
426
495
  const taskId = Math.random().toString(36).slice(2, 10);
@@ -503,20 +572,18 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles({ maxFile
503
572
  parsed,
504
573
  attachmentPathsByAnnotationId
505
574
  );
575
+ } else if (input.messageKind === "chat") {
576
+ promptContent = buildChatPrompt(
577
+ config,
578
+ input.pageRoute ?? "/",
579
+ input.renderedUrl ?? "",
580
+ input.notes
581
+ );
506
582
  } else {
507
583
  promptContent = buildNonVisualPrompt(config, input.notes);
508
584
  }
509
585
  yield { type: "status", message: "Running agent..." };
510
- let systemPrompt = "";
511
- const agentKitPath = path.join(
512
- projectDir,
513
- "agent-kit",
514
- "designer",
515
- "INSTRUCTIONS.md"
516
- );
517
- if (fs$1.existsSync(agentKitPath)) {
518
- systemPrompt = fs$1.readFileSync(agentKitPath, "utf-8");
519
- }
586
+ const systemPrompt = loadAgentKitSystemPrompt(projectDir);
520
587
  const routeKey = normalizePageRoute(input.pageRoute);
521
588
  const sessionPlan = await getOrCreateSessionIdForRoute(
522
589
  projectDir,
@@ -592,6 +659,2590 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles({ maxFile
592
659
  }
593
660
  yield { type: "done" };
594
661
  });
662
+ const CATALOG = [
663
+ {
664
+ pluginName: "wix-server-client",
665
+ packageName: "@jay-framework/wix-server-client",
666
+ description: "Wix API client and authentication",
667
+ kind: "service-only",
668
+ requires: [],
669
+ showInAddPagePicker: false,
670
+ configFiles: ["config/.wix.yaml"]
671
+ },
672
+ {
673
+ pluginName: "wix-cart",
674
+ packageName: "@jay-framework/wix-cart",
675
+ description: "Shopping cart headless components",
676
+ kind: "headless",
677
+ requires: ["wix-server-client"],
678
+ showInAddPagePicker: true
679
+ },
680
+ {
681
+ pluginName: "wix-stores",
682
+ packageName: "@jay-framework/wix-stores",
683
+ description: "Wix Stores product search, product page, categories",
684
+ kind: "headless",
685
+ requires: ["wix-server-client", "wix-cart"],
686
+ showInAddPagePicker: true,
687
+ configFiles: ["config/.wix-stores.yaml"]
688
+ },
689
+ {
690
+ pluginName: "wix-stores-v1",
691
+ packageName: "@jay-framework/wix-stores-v1",
692
+ description: "Wix Stores v1 catalog API",
693
+ kind: "headless",
694
+ requires: ["wix-server-client", "wix-cart"],
695
+ showInAddPagePicker: true
696
+ },
697
+ {
698
+ pluginName: "wix-data",
699
+ packageName: "@jay-framework/wix-data",
700
+ description: "Wix Data collections CMS",
701
+ kind: "headless",
702
+ requires: ["wix-server-client"],
703
+ showInAddPagePicker: true,
704
+ configFiles: ["config/.wix-data.yaml"]
705
+ },
706
+ {
707
+ pluginName: "wix-media",
708
+ packageName: "@jay-framework/wix-media",
709
+ description: "Wix media service (no page components)",
710
+ kind: "service-only",
711
+ requires: ["wix-server-client"],
712
+ showInAddPagePicker: true
713
+ },
714
+ {
715
+ pluginName: "ui-kit",
716
+ packageName: "@jay-framework/ui-kit",
717
+ description: "Jay UI kit headless components",
718
+ kind: "headless",
719
+ requires: [],
720
+ showInAddPagePicker: true
721
+ },
722
+ {
723
+ pluginName: "gemini-agent",
724
+ packageName: "@jay-framework/gemini-agent",
725
+ description: "Gemini AI agent plugin",
726
+ kind: "headless",
727
+ requires: [],
728
+ showInAddPagePicker: true,
729
+ configFiles: ["config/.gemini-agent.yaml"]
730
+ },
731
+ {
732
+ pluginName: "webmcp",
733
+ packageName: "@jay-framework/webmcp",
734
+ description: "Web MCP tooling",
735
+ kind: "tooling",
736
+ requires: [],
737
+ showInAddPagePicker: false
738
+ },
739
+ {
740
+ pluginName: "aiditor",
741
+ packageName: "@jay-framework/aiditor",
742
+ description: "AIditor self plugin",
743
+ kind: "tooling",
744
+ requires: [],
745
+ showInAddPagePicker: false
746
+ }
747
+ ];
748
+ function getJayPluginCatalog() {
749
+ return CATALOG.map((e) => ({ ...e, requires: [...e.requires] }));
750
+ }
751
+ function getCatalogEntry(pluginName) {
752
+ return CATALOG.find((e) => e.pluginName === pluginName);
753
+ }
754
+ const DEFAULT_COMPONENT_KEYS = {
755
+ "product-page": "p",
756
+ "product-search": "search",
757
+ "cart-indicator": "cart",
758
+ "cart-page": "cartPage",
759
+ "category-list": "categories"
760
+ };
761
+ function getDefaultComponentKey(contractName) {
762
+ if (contractName in DEFAULT_COMPONENT_KEYS) {
763
+ return DEFAULT_COMPONENT_KEYS[contractName];
764
+ }
765
+ return contractName.charAt(0) || "c";
766
+ }
767
+ class AddPagePromptLoadError extends Error {
768
+ constructor(message) {
769
+ super(message);
770
+ this.name = "AddPagePromptLoadError";
771
+ }
772
+ }
773
+ function resolveAddPageAgentsRoot(fromModuleUrl = import.meta.url) {
774
+ const thisDir = path.dirname(fileURLToPath(fromModuleUrl));
775
+ const candidates = [
776
+ path.join(thisDir, "add-page-agent"),
777
+ path.join(thisDir, "..", "add-page-agent"),
778
+ path.join(thisDir, "..", "..", "add-page-agent"),
779
+ path.join(thisDir, "..", "..", "..", "add-page-agent")
780
+ ];
781
+ for (const root of candidates) {
782
+ const probe = path.join(root, "system.md");
783
+ if (fs$1.existsSync(probe)) {
784
+ return root;
785
+ }
786
+ }
787
+ throw new AddPagePromptLoadError(
788
+ `add-page-agent root not found (searched from ${thisDir})`
789
+ );
790
+ }
791
+ async function loadAddPageAgentPrompts(options) {
792
+ const agentsRoot = resolveAddPageAgentsRoot(import.meta.url);
793
+ const systemPath = path.join(agentsRoot, "system.md");
794
+ const skillPath = path.join(agentsRoot, "SKILL.md");
795
+ let system;
796
+ let skill;
797
+ try {
798
+ system = await readFile(systemPath, "utf-8");
799
+ } catch {
800
+ throw new AddPagePromptLoadError(`missing system prompt: ${systemPath}`);
801
+ }
802
+ try {
803
+ skill = await readFile(skillPath, "utf-8");
804
+ } catch {
805
+ throw new AddPagePromptLoadError(`missing skill document: ${skillPath}`);
806
+ }
807
+ return { system, skill };
808
+ }
809
+ async function listDirFiles(dir, prefix) {
810
+ try {
811
+ const names = await readdir(dir);
812
+ return names.map((n) => `${prefix}/${n}`);
813
+ } catch {
814
+ return [];
815
+ }
816
+ }
817
+ function buildPluginManifestPromptSection(selectedPlugins) {
818
+ const lines = [
819
+ "",
820
+ "## Page plugin manifest (authoritative)",
821
+ "",
822
+ "The user selected these headless components for this page. You MUST:",
823
+ '1. Add `<script type="application/jay-headless" plugin="..." contract="..." key="...">` for each entry',
824
+ "2. Bind UI using contract ViewState and refs per agent-kit designer guides",
825
+ "3. Run jay-stack validate",
826
+ "",
827
+ "| Plugin | Contract | Key | Notes |",
828
+ "| --- | --- | --- | --- |"
829
+ ];
830
+ for (const plugin of selectedPlugins) {
831
+ for (const contract of plugin.contracts) {
832
+ const key = contract.componentKey ?? getDefaultComponentKey(contract.contractName);
833
+ lines.push(
834
+ `| ${plugin.packageName} | ${contract.contractName} | ${key} | |`
835
+ );
836
+ }
837
+ }
838
+ lines.push(
839
+ "",
840
+ "If Wix config is incomplete, implement layout and headless imports; live store data may be unavailable until config/.wix.yaml is filled.",
841
+ ""
842
+ );
843
+ return lines.join("\n");
844
+ }
845
+ async function buildAddPagePrompt(input) {
846
+ const { system, skill } = await loadAddPageAgentPrompts();
847
+ const contentPath = path.join(input.requestDir, "content.md");
848
+ const designPath = path.join(input.requestDir, "design.md");
849
+ const references = await listDirFiles(
850
+ path.join(input.requestDir, "references"),
851
+ "references"
852
+ );
853
+ const assets = await listDirFiles(
854
+ path.join(input.requestDir, "assets"),
855
+ "assets"
856
+ );
857
+ const designerGuide = path.join(
858
+ input.projectDir,
859
+ "agent-kit",
860
+ "designer",
861
+ "INSTRUCTIONS.md"
862
+ );
863
+ const developerGuide = path.join(
864
+ input.projectDir,
865
+ "agent-kit",
866
+ "developer",
867
+ "INSTRUCTIONS.md"
868
+ );
869
+ const retryNote = input.isRetry ? `
870
+ ## Retry mode
871
+ The page at route \`${input.pageRoute}\` may already have partial files from a previous run. Read existing page files and **fix** validate errors rather than recreating from scratch.
872
+ ` : "";
873
+ const manifestSection = input.selectedPlugins && input.selectedPlugins.length > 0 ? buildPluginManifestPromptSection(input.selectedPlugins) : "";
874
+ const userMessage = `# Add Page task
875
+
876
+ Create **one** Jay Stack page at route: \`${input.pageRoute}\` (${input.routeKind}).
877
+ ${retryNote}
878
+ ## Brief files (read these first)
879
+
880
+ - Content (behavior, plugins, contracts): \`${contentPath}\`
881
+ - Design (layout, typography, colors): \`${designPath}\`
882
+
883
+ ## Reference attachments
884
+
885
+ - Inspiration only (do not copy verbatim unless brief says so): ${references.length ? references.join(", ") : "(none)"}
886
+ - Assets to embed (copy into project when cited in brief): ${assets.length ? assets.join(", ") : "(none)"}
887
+
888
+ Asset files live under the request folder. **Copy** cited assets from \`${input.requestDir}\` into appropriate project paths (\`public/\`, etc.) when implementing.
889
+
890
+ ## Project guides
891
+
892
+ - Designer: \`${designerGuide}\`
893
+ - Developer: \`${developerGuide}\`
894
+
895
+ ## Output
896
+
897
+ - Create page files under \`src/pages/\` matching the route per Jay directory routing conventions.
898
+ - Match user intent from content.md and design.md as closely as possible.
899
+ - Reuse existing site patterns (header, footer, styling) from other pages when design.md requests it.
900
+ - Summarize files created and how they cover the brief when done.
901
+ ${manifestSection}
902
+ ---
903
+
904
+ ${skill}`;
905
+ return { system, userMessage };
906
+ }
907
+ async function* streamAddPageAgentQuery(input) {
908
+ const { system, userMessage } = await buildAddPagePrompt(input);
909
+ const routeKey = normalizePageRoute(input.pageRoute);
910
+ if (!tryBeginRouteQuery(routeKey)) {
911
+ yield {
912
+ type: "error",
913
+ message: "Another agent task is already running for this page. Wait for it to finish, then try again."
914
+ };
915
+ return;
916
+ }
917
+ const sessionPlan = await getOrCreateSessionIdForRoute(
918
+ input.projectDir,
919
+ routeKey
920
+ );
921
+ const sharedOptions = {
922
+ cwd: input.projectDir,
923
+ tools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
924
+ allowedTools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
925
+ maxTurns: 40,
926
+ persistSession: true,
927
+ systemPrompt: system
928
+ };
929
+ try {
930
+ async function* streamQuery(opts) {
931
+ for await (const message of query({
932
+ prompt: userMessage,
933
+ options: { ...sharedOptions, ...opts }
934
+ })) {
935
+ for (const chunk of transformSDKMessage(
936
+ message
937
+ )) {
938
+ yield chunk;
939
+ }
940
+ }
941
+ }
942
+ let didStaleResumeRetry = false;
943
+ try {
944
+ if (sessionPlan.useResume) {
945
+ yield* streamQuery({ resume: sessionPlan.sessionId });
946
+ } else {
947
+ yield* streamQuery({
948
+ sessionId: sessionPlan.sessionId,
949
+ title: routeKey
950
+ });
951
+ }
952
+ } catch (err) {
953
+ if (sessionPlan.useResume && !didStaleResumeRetry) {
954
+ didStaleResumeRetry = true;
955
+ const fresh = await replaceRouteSessionAfterStaleResume(
956
+ input.projectDir,
957
+ routeKey
958
+ );
959
+ try {
960
+ yield* streamQuery({
961
+ sessionId: fresh.sessionId,
962
+ title: routeKey
963
+ });
964
+ } catch (err2) {
965
+ yield {
966
+ type: "error",
967
+ message: err2 instanceof Error ? err2.message : String(err2)
968
+ };
969
+ }
970
+ } else {
971
+ yield {
972
+ type: "error",
973
+ message: err instanceof Error ? err.message : String(err)
974
+ };
975
+ }
976
+ }
977
+ } finally {
978
+ endRouteQuery(routeKey);
979
+ }
980
+ }
981
+ const PAGE_REQUESTS_DIR_SEGMENT = ".aiditor/page-requests";
982
+ function getPageRequestsRoot(projectRoot) {
983
+ return path.join(projectRoot, PAGE_REQUESTS_DIR_SEGMENT);
984
+ }
985
+ function getPageRequestDir(projectRoot, requestId) {
986
+ return path.join(getPageRequestsRoot(projectRoot), requestId);
987
+ }
988
+ function generatePageRequestId() {
989
+ return crypto.randomUUID();
990
+ }
991
+ function sanitizeAttachmentFilename(name) {
992
+ const base = path.basename(name).toLowerCase();
993
+ const sanitized = base.replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-");
994
+ return sanitized || "file";
995
+ }
996
+ async function ensurePageRequestDir(projectRoot, requestId) {
997
+ const requestDir = getPageRequestDir(projectRoot, requestId);
998
+ await fs.mkdir(path.join(requestDir, "assets"), { recursive: true });
999
+ await fs.mkdir(path.join(requestDir, "references"), { recursive: true });
1000
+ return requestDir;
1001
+ }
1002
+ async function createPageRequest(projectRoot, input) {
1003
+ const requestId = input.requestId ?? generatePageRequestId();
1004
+ const requestDir = await ensurePageRequestDir(projectRoot, requestId);
1005
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1006
+ const request = {
1007
+ requestId,
1008
+ kind: "add_page",
1009
+ pageRoute: input.pageRoute,
1010
+ routeKind: input.routeKind,
1011
+ status: "draft",
1012
+ createdAt: now,
1013
+ updatedAt: now,
1014
+ contentMdPath: "content.md",
1015
+ designMdPath: "design.md",
1016
+ references: [],
1017
+ assets: []
1018
+ };
1019
+ await writePageRequest(projectRoot, request);
1020
+ await fs.writeFile(path.join(requestDir, "content.md"), "", "utf-8");
1021
+ await fs.writeFile(path.join(requestDir, "design.md"), "", "utf-8");
1022
+ return { requestDir, request };
1023
+ }
1024
+ async function readPageRequest(projectRoot, requestId) {
1025
+ const requestPath = path.join(
1026
+ getPageRequestDir(projectRoot, requestId),
1027
+ "request.json"
1028
+ );
1029
+ try {
1030
+ const raw = await fs.readFile(requestPath, "utf-8");
1031
+ return JSON.parse(raw);
1032
+ } catch (e) {
1033
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
1034
+ if (code === "ENOENT") return null;
1035
+ throw e;
1036
+ }
1037
+ }
1038
+ async function writePageRequestAtomic(requestDir, request) {
1039
+ const requestPath = path.join(requestDir, "request.json");
1040
+ const tmp = `${requestPath}.${process.pid}.${Date.now()}.tmp`;
1041
+ await fs.writeFile(tmp, `${JSON.stringify(request, null, 2)}
1042
+ `, "utf-8");
1043
+ await fs.rename(tmp, requestPath);
1044
+ }
1045
+ async function writePageRequest(projectRoot, request) {
1046
+ const requestDir = getPageRequestDir(projectRoot, request.requestId);
1047
+ await writePageRequestAtomic(requestDir, request);
1048
+ }
1049
+ async function updatePageRequestStatus(projectRoot, requestId, status) {
1050
+ const request = await readPageRequest(projectRoot, requestId);
1051
+ if (!request) {
1052
+ throw new Error(`Page request not found: ${requestId}`);
1053
+ }
1054
+ const updated = {
1055
+ ...request,
1056
+ status,
1057
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1058
+ };
1059
+ await writePageRequest(projectRoot, updated);
1060
+ return updated;
1061
+ }
1062
+ async function writePageRequestMarkdown(projectRoot, requestId, input) {
1063
+ const requestDir = getPageRequestDir(projectRoot, requestId);
1064
+ await fs.writeFile(
1065
+ path.join(requestDir, "content.md"),
1066
+ input.contentMd,
1067
+ "utf-8"
1068
+ );
1069
+ await fs.writeFile(
1070
+ path.join(requestDir, "design.md"),
1071
+ input.designMd,
1072
+ "utf-8"
1073
+ );
1074
+ const request = await readPageRequest(projectRoot, requestId);
1075
+ if (request) {
1076
+ await writePageRequest(projectRoot, {
1077
+ ...request,
1078
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1079
+ });
1080
+ }
1081
+ }
1082
+ async function addPageRequestAttachment(projectRoot, requestId, entry, role) {
1083
+ const request = await readPageRequest(projectRoot, requestId);
1084
+ if (!request) {
1085
+ throw new Error(`Page request not found: ${requestId}`);
1086
+ }
1087
+ const updated = {
1088
+ ...request,
1089
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1090
+ references: role === "reference" ? [
1091
+ ...request.references.filter((r) => r.id !== entry.id),
1092
+ entry
1093
+ ] : request.references,
1094
+ assets: role === "asset" ? [
1095
+ ...request.assets.filter((a) => a.id !== entry.id),
1096
+ entry
1097
+ ] : request.assets
1098
+ };
1099
+ await writePageRequest(projectRoot, updated);
1100
+ return updated;
1101
+ }
1102
+ async function reclassifyPageRequestAttachment(projectRoot, requestId, attachmentId, newRole) {
1103
+ const request = await readPageRequest(projectRoot, requestId);
1104
+ if (!request) {
1105
+ throw new Error(`Page request not found: ${requestId}`);
1106
+ }
1107
+ const requestDir = getPageRequestDir(projectRoot, requestId);
1108
+ const refIx = request.references.findIndex((r) => r.id === attachmentId);
1109
+ const assetIx = request.assets.findIndex((a) => a.id === attachmentId);
1110
+ let relPath;
1111
+ let label;
1112
+ if (refIx >= 0) {
1113
+ relPath = request.references[refIx].path;
1114
+ label = request.references[refIx].label;
1115
+ } else if (assetIx >= 0) {
1116
+ relPath = request.assets[assetIx].path;
1117
+ } else {
1118
+ throw new Error(`Attachment not found: ${attachmentId}`);
1119
+ }
1120
+ const filename = path.basename(relPath);
1121
+ const destSubdir = newRole === "asset" ? "assets" : "references";
1122
+ const srcAbs = path.join(requestDir, relPath);
1123
+ const destRel = `${destSubdir}/${filename}`;
1124
+ const destAbs = path.join(requestDir, destRel);
1125
+ await fs.rename(srcAbs, destAbs);
1126
+ const references = request.references.filter((r) => r.id !== attachmentId);
1127
+ const assets = request.assets.filter((a) => a.id !== attachmentId);
1128
+ if (newRole === "reference") {
1129
+ references.push({ id: attachmentId, path: destRel, label });
1130
+ } else {
1131
+ assets.push({
1132
+ id: attachmentId,
1133
+ path: destRel,
1134
+ markdownRef: destRel
1135
+ });
1136
+ }
1137
+ const updated = {
1138
+ ...request,
1139
+ references,
1140
+ assets,
1141
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1142
+ };
1143
+ await writePageRequest(projectRoot, updated);
1144
+ return updated;
1145
+ }
1146
+ function inferRouteKind(route) {
1147
+ return /\[[^\]/]+\]/.test(route) ? "dynamic" : "static";
1148
+ }
1149
+ function validatePageRouteSyntax(route) {
1150
+ const trimmed = route.trim();
1151
+ if (!trimmed) {
1152
+ return { valid: false, error: "Route is required." };
1153
+ }
1154
+ if (!trimmed.startsWith("/")) {
1155
+ return { valid: false, error: "Route must start with /." };
1156
+ }
1157
+ const normalized = normalizePageRoute(trimmed);
1158
+ if (normalized.includes("//")) {
1159
+ return { valid: false, error: "Route contains invalid segments." };
1160
+ }
1161
+ const dynamicSegments = normalized.match(/\[([^\]]*)\]/g) ?? [];
1162
+ for (const seg of dynamicSegments) {
1163
+ const inner = seg.slice(1, -1).trim();
1164
+ if (!inner) {
1165
+ return {
1166
+ valid: false,
1167
+ error: "Dynamic segments must have a param name, e.g. [slug]."
1168
+ };
1169
+ }
1170
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(inner)) {
1171
+ return {
1172
+ valid: false,
1173
+ error: `Invalid dynamic segment ${seg}. Use [paramName] format.`
1174
+ };
1175
+ }
1176
+ }
1177
+ return { valid: true };
1178
+ }
1179
+ function routeExistsInProject(normalizedRoute, projectRoutes) {
1180
+ return projectRoutes.some(
1181
+ (r) => normalizePageRoute(r.path) === normalizedRoute
1182
+ );
1183
+ }
1184
+ async function runAddPageValidate(projectDir) {
1185
+ return new Promise((resolve) => {
1186
+ const child = spawn("npx", ["jay-stack-cli", "validate"], {
1187
+ cwd: projectDir,
1188
+ shell: true,
1189
+ env: process.env
1190
+ });
1191
+ let stdout = "";
1192
+ let stderr = "";
1193
+ child.stdout.on("data", (chunk) => {
1194
+ stdout += chunk.toString();
1195
+ });
1196
+ child.stderr.on("data", (chunk) => {
1197
+ stderr += chunk.toString();
1198
+ });
1199
+ child.on("close", (code) => {
1200
+ const combined = `${stdout}
1201
+ ${stderr}`.trim();
1202
+ if (code === 0) {
1203
+ resolve({ ok: true, errors: [], stdout, stderr });
1204
+ return;
1205
+ }
1206
+ const errors = combined.split("\n").map((line) => line.trim()).filter(Boolean);
1207
+ resolve({
1208
+ ok: false,
1209
+ errors: errors.length > 0 ? errors : [`validate exited with code ${code}`],
1210
+ stdout,
1211
+ stderr
1212
+ });
1213
+ });
1214
+ child.on("error", (err) => {
1215
+ resolve({
1216
+ ok: false,
1217
+ errors: [err.message],
1218
+ stdout,
1219
+ stderr
1220
+ });
1221
+ });
1222
+ });
1223
+ }
1224
+ const PAGES_DIR_CANDIDATES = ["src/pages", "pages"];
1225
+ const PAGE_BRIEF_DIR_NAME = "add-page-brief";
1226
+ function resolveJayHtmlPath(projectDir, jayHtmlPath) {
1227
+ return path.isAbsolute(jayHtmlPath) ? jayHtmlPath : path.join(projectDir, jayHtmlPath);
1228
+ }
1229
+ function getPageBriefDirForJayHtml(jayHtmlPath) {
1230
+ return path.join(path.dirname(jayHtmlPath), PAGE_BRIEF_DIR_NAME);
1231
+ }
1232
+ function getPageBriefContentPath(briefDir) {
1233
+ return path.join(briefDir, "content.md");
1234
+ }
1235
+ function getPageBriefDesignPath(briefDir) {
1236
+ return path.join(briefDir, "design.md");
1237
+ }
1238
+ async function resolveJayHtmlPathForPageRoute(projectDir, normalizedRoute, cachedRoutes) {
1239
+ if (cachedRoutes) {
1240
+ const fromCache = cachedRoutes.find(
1241
+ (r) => normalizePageRoute(r.path) === normalizedRoute
1242
+ );
1243
+ if (fromCache?.jayHtmlPath) return fromCache.jayHtmlPath;
1244
+ }
1245
+ for (const rel of PAGES_DIR_CANDIDATES) {
1246
+ const pagesDir = path.join(projectDir, rel);
1247
+ try {
1248
+ await fs.access(pagesDir);
1249
+ } catch {
1250
+ continue;
1251
+ }
1252
+ const routes = await scanRoutes(pagesDir, {
1253
+ jayHtmlFilename: "page.jay-html",
1254
+ compFilename: "page.ts"
1255
+ });
1256
+ const match = routes.find(
1257
+ (r) => normalizePageRoute(r.rawRoute) === normalizedRoute
1258
+ );
1259
+ if (match?.jayHtmlPath) return match.jayHtmlPath;
1260
+ }
1261
+ return null;
1262
+ }
1263
+ async function tryBackfillPageBriefForRoute(projectDir, jayHtmlPath, pageRoute) {
1264
+ if (await pageBriefExists(projectDir, jayHtmlPath)) {
1265
+ return true;
1266
+ }
1267
+ const normalized = normalizePageRoute(pageRoute);
1268
+ const requestsRoot = getPageRequestsRoot(projectDir);
1269
+ let requestDirs = [];
1270
+ try {
1271
+ requestDirs = await fs.readdir(requestsRoot);
1272
+ } catch {
1273
+ return false;
1274
+ }
1275
+ let best = null;
1276
+ for (const dir of requestDirs) {
1277
+ const request = await readPageRequest(projectDir, dir);
1278
+ if (!request || request.status !== "completed" || normalizePageRoute(request.pageRoute) !== normalized) {
1279
+ continue;
1280
+ }
1281
+ if (!best || new Date(request.updatedAt).getTime() > new Date(best.updatedAt).getTime()) {
1282
+ best = request;
1283
+ }
1284
+ }
1285
+ if (!best) return false;
1286
+ const requestDir = getPageRequestDir(projectDir, best.requestId);
1287
+ let contentMd = "";
1288
+ let designMd = "";
1289
+ try {
1290
+ contentMd = await fs.readFile(path.join(requestDir, "content.md"), "utf-8");
1291
+ designMd = await fs.readFile(path.join(requestDir, "design.md"), "utf-8");
1292
+ } catch {
1293
+ return false;
1294
+ }
1295
+ await savePageBriefFromRequest(projectDir, jayHtmlPath, {
1296
+ requestId: best.requestId,
1297
+ pageRoute: normalized,
1298
+ contentMd,
1299
+ designMd
1300
+ });
1301
+ return true;
1302
+ }
1303
+ async function pageBriefExists(projectDir, jayHtmlPath) {
1304
+ const absJayHtml = resolveJayHtmlPath(projectDir, jayHtmlPath);
1305
+ const briefDir = getPageBriefDirForJayHtml(absJayHtml);
1306
+ try {
1307
+ await fs.access(getPageBriefContentPath(briefDir));
1308
+ return true;
1309
+ } catch {
1310
+ return false;
1311
+ }
1312
+ }
1313
+ async function copyDirFiles(srcDir, destDir) {
1314
+ await fs.mkdir(destDir, { recursive: true });
1315
+ let entries = [];
1316
+ try {
1317
+ entries = await fs.readdir(srcDir);
1318
+ } catch (e) {
1319
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
1320
+ if (code === "ENOENT") return;
1321
+ throw e;
1322
+ }
1323
+ for (const name of entries) {
1324
+ const src = path.join(srcDir, name);
1325
+ const dest = path.join(destDir, name);
1326
+ const stat = await fs.stat(src);
1327
+ if (stat.isFile()) {
1328
+ await fs.copyFile(src, dest);
1329
+ }
1330
+ }
1331
+ }
1332
+ async function savePageBriefFromRequest(projectDir, jayHtmlPath, input) {
1333
+ const absJayHtml = resolveJayHtmlPath(projectDir, jayHtmlPath);
1334
+ const briefDir = getPageBriefDirForJayHtml(absJayHtml);
1335
+ const requestDir = getPageRequestDir(projectDir, input.requestId);
1336
+ await fs.mkdir(path.join(briefDir, "assets"), { recursive: true });
1337
+ await fs.mkdir(path.join(briefDir, "references"), { recursive: true });
1338
+ await fs.writeFile(
1339
+ getPageBriefContentPath(briefDir),
1340
+ input.contentMd,
1341
+ "utf-8"
1342
+ );
1343
+ await fs.writeFile(getPageBriefDesignPath(briefDir), input.designMd, "utf-8");
1344
+ await copyDirFiles(
1345
+ path.join(requestDir, "assets"),
1346
+ path.join(briefDir, "assets")
1347
+ );
1348
+ await copyDirFiles(
1349
+ path.join(requestDir, "references"),
1350
+ path.join(briefDir, "references")
1351
+ );
1352
+ const manifest = {
1353
+ sourceRoute: normalizePageRoute(input.pageRoute),
1354
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1355
+ requestId: input.requestId
1356
+ };
1357
+ await fs.writeFile(
1358
+ path.join(briefDir, "brief.json"),
1359
+ `${JSON.stringify(manifest, null, 2)}
1360
+ `,
1361
+ "utf-8"
1362
+ );
1363
+ }
1364
+ async function readOptionalJson(filePath) {
1365
+ try {
1366
+ const raw = await fs.readFile(filePath, "utf-8");
1367
+ return JSON.parse(raw);
1368
+ } catch {
1369
+ return null;
1370
+ }
1371
+ }
1372
+ async function listBriefAttachments(briefDir) {
1373
+ const attachments = [];
1374
+ for (const role of ["asset", "reference"]) {
1375
+ const subdir = role === "asset" ? "assets" : "references";
1376
+ const dirPath = path.join(briefDir, subdir);
1377
+ let names = [];
1378
+ try {
1379
+ names = await fs.readdir(dirPath);
1380
+ } catch {
1381
+ continue;
1382
+ }
1383
+ for (const name of names) {
1384
+ const abs = path.join(dirPath, name);
1385
+ const stat = await fs.stat(abs);
1386
+ if (!stat.isFile()) continue;
1387
+ attachments.push({
1388
+ id: crypto.randomUUID(),
1389
+ path: `${subdir}/${name}`,
1390
+ role,
1391
+ filename: name
1392
+ });
1393
+ }
1394
+ }
1395
+ return attachments;
1396
+ }
1397
+ async function readPageBrief(projectDir, jayHtmlPath) {
1398
+ const absJayHtml = resolveJayHtmlPath(projectDir, jayHtmlPath);
1399
+ const briefDir = getPageBriefDirForJayHtml(absJayHtml);
1400
+ const contentPath = getPageBriefContentPath(briefDir);
1401
+ try {
1402
+ await fs.access(contentPath);
1403
+ } catch {
1404
+ return null;
1405
+ }
1406
+ const contentMd = await fs.readFile(contentPath, "utf-8");
1407
+ let designMd = "";
1408
+ try {
1409
+ designMd = await fs.readFile(getPageBriefDesignPath(briefDir), "utf-8");
1410
+ } catch {
1411
+ }
1412
+ const manifest = await readOptionalJson(
1413
+ path.join(briefDir, "brief.json")
1414
+ ) ?? {
1415
+ sourceRoute: "",
1416
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1417
+ requestId: ""
1418
+ };
1419
+ const attachments = await listBriefAttachments(briefDir);
1420
+ return { manifest, contentMd, designMd, attachments };
1421
+ }
1422
+ function isDynamicSegment(segment) {
1423
+ return segment.includes("[");
1424
+ }
1425
+ function suggestCopyRoute(sourceRoute, existingRoutes) {
1426
+ const normalized = normalizePageRoute(sourceRoute);
1427
+ const existing = new Set(
1428
+ existingRoutes.map((r) => normalizePageRoute(r.path))
1429
+ );
1430
+ const segments = normalized.split("/").filter(Boolean);
1431
+ let lastStaticIdx = -1;
1432
+ for (let i = segments.length - 1; i >= 0; i--) {
1433
+ if (!isDynamicSegment(segments[i])) {
1434
+ lastStaticIdx = i;
1435
+ break;
1436
+ }
1437
+ }
1438
+ const buildRoute = (suffix) => {
1439
+ if (lastStaticIdx < 0) {
1440
+ return normalizePageRoute(`${normalized}${suffix}`);
1441
+ }
1442
+ const next = [...segments];
1443
+ next[lastStaticIdx] = `${next[lastStaticIdx]}${suffix}`;
1444
+ return normalizePageRoute(`/${next.join("/")}`);
1445
+ };
1446
+ let candidate = buildRoute("-copy");
1447
+ if (!existing.has(candidate)) return candidate;
1448
+ for (let n = 2; n < 100; n++) {
1449
+ candidate = buildRoute(`-copy-${n}`);
1450
+ if (!existing.has(candidate)) return candidate;
1451
+ }
1452
+ return buildRoute(`-copy-${Date.now()}`);
1453
+ }
1454
+ async function clonePageBriefToRequest(projectDir, jayHtmlPath, input) {
1455
+ const brief = await readPageBrief(projectDir, jayHtmlPath);
1456
+ if (!brief) {
1457
+ throw new Error("No Add Page brief found for this page.");
1458
+ }
1459
+ const pageRoute = normalizePageRoute(input.pageRoute);
1460
+ const routeKind = input.routeKind ?? inferRouteKind(pageRoute);
1461
+ const { request } = await createPageRequest(projectDir, {
1462
+ pageRoute,
1463
+ routeKind
1464
+ });
1465
+ const absJayHtml = resolveJayHtmlPath(projectDir, jayHtmlPath);
1466
+ const briefDir = getPageBriefDirForJayHtml(absJayHtml);
1467
+ const requestDir = getPageRequestDir(projectDir, request.requestId);
1468
+ await fs.writeFile(
1469
+ path.join(requestDir, "content.md"),
1470
+ brief.contentMd,
1471
+ "utf-8"
1472
+ );
1473
+ await fs.writeFile(
1474
+ path.join(requestDir, "design.md"),
1475
+ brief.designMd,
1476
+ "utf-8"
1477
+ );
1478
+ const attachments = [];
1479
+ for (const attachment of brief.attachments) {
1480
+ const src = path.join(briefDir, attachment.path);
1481
+ const dest = path.join(requestDir, attachment.path);
1482
+ await fs.mkdir(path.dirname(dest), { recursive: true });
1483
+ await fs.copyFile(src, dest);
1484
+ const id = generatePageRequestId();
1485
+ if (attachment.role === "reference") {
1486
+ const entry = {
1487
+ id,
1488
+ path: attachment.path,
1489
+ label: attachment.filename
1490
+ };
1491
+ await addPageRequestAttachment(
1492
+ projectDir,
1493
+ request.requestId,
1494
+ entry,
1495
+ "reference"
1496
+ );
1497
+ attachments.push({ ...attachment, id });
1498
+ } else {
1499
+ const entry = {
1500
+ id,
1501
+ path: attachment.path,
1502
+ markdownRef: attachment.path
1503
+ };
1504
+ await addPageRequestAttachment(
1505
+ projectDir,
1506
+ request.requestId,
1507
+ entry,
1508
+ "asset"
1509
+ );
1510
+ attachments.push({ ...attachment, id });
1511
+ }
1512
+ }
1513
+ return {
1514
+ requestId: request.requestId,
1515
+ request,
1516
+ contentMd: brief.contentMd,
1517
+ designMd: brief.designMd,
1518
+ attachments
1519
+ };
1520
+ }
1521
+ const BRIEF_FILL_MAX_IMAGES = 5;
1522
+ const BRIEF_FILL_MAX_TURNS = 3;
1523
+ const BRIEF_FILL_PROMPT_FILES = {
1524
+ brief: "add-page-figma-brief-prompt.md",
1525
+ designFromImage: "add-page-figma-design-from-image-prompt.md",
1526
+ designDefinitions: "add-page-figma-design-definitions-prompt.md"
1527
+ };
1528
+ function extractCopyPastePromptBody(markdown) {
1529
+ const parts = markdown.split(/\r?\n---\r?\n/);
1530
+ if (parts.length < 2) return markdown.trim();
1531
+ return parts[1].trim();
1532
+ }
1533
+ function moduleDirectory() {
1534
+ return path.dirname(fileURLToPath(import.meta.url));
1535
+ }
1536
+ function resolveBriefFillPromptsDir() {
1537
+ const candidates = [
1538
+ path.join(moduleDirectory(), "prompts"),
1539
+ path.join(moduleDirectory(), "../../../../../docs")
1540
+ ];
1541
+ for (const dir of candidates) {
1542
+ const briefPath = path.join(dir, BRIEF_FILL_PROMPT_FILES.brief);
1543
+ if (fs$1.existsSync(briefPath)) return dir;
1544
+ }
1545
+ throw new Error(
1546
+ "Brief fill prompt files not found. Run `yarn build` in @jay-framework/aiditor or ensure docs/add-page-figma-*.md exist."
1547
+ );
1548
+ }
1549
+ function readPromptFile(filename, promptsDir) {
1550
+ const dir = resolveBriefFillPromptsDir();
1551
+ const filePath = path.join(dir, filename);
1552
+ const raw = fs$1.readFileSync(filePath, "utf-8");
1553
+ return extractCopyPastePromptBody(raw);
1554
+ }
1555
+ const CONTENT_FILL_SUFFIX = `
1556
+
1557
+ ---
1558
+
1559
+ ## AIditor — Fill from image (Content field)
1560
+
1561
+ You are invoked from AIditor **Fill from image** on the **Content** composer field.
1562
+
1563
+ Follow the prompt above. Produce **only**:
1564
+
1565
+ - **Section 1** — Suggested page route (when inferable from the screenshot or context notes)
1566
+ - **Section 2** — Content brief (use the full structure specified above)
1567
+
1568
+ Do **not** output Section 3 (Design brief), Section 4 (Checklist), or any Design markdown. The user fills Design in a separate step.`;
1569
+ const DESIGN_FILL_SUFFIX = `
1570
+
1571
+ ---
1572
+
1573
+ ## AIditor — Fill from image (Design field)
1574
+
1575
+ You are invoked from AIditor **Fill from image** on the **Design** composer field.
1576
+
1577
+ Follow the prompt above. Produce **only** the Design markdown document (one block for AIditor → Design).
1578
+
1579
+ Do **not** output a Content brief, suggested route, or plugin behavior sections.`;
1580
+ const cache = /* @__PURE__ */ new Map();
1581
+ function loadCached(key, loader) {
1582
+ const hit = cache.get(key);
1583
+ if (hit !== void 0) return hit;
1584
+ const value = loader();
1585
+ cache.set(key, value);
1586
+ return value;
1587
+ }
1588
+ function loadBriefFillSystemPrompt(target, promptsDir) {
1589
+ const cacheKey = `${target}:${"default"}`;
1590
+ return loadCached(cacheKey, () => {
1591
+ if (target === "content") {
1592
+ const briefBody = readPromptFile(
1593
+ BRIEF_FILL_PROMPT_FILES.brief
1594
+ );
1595
+ return `${briefBody}${CONTENT_FILL_SUFFIX}`;
1596
+ }
1597
+ const designFromImageBody = readPromptFile(
1598
+ BRIEF_FILL_PROMPT_FILES.designFromImage
1599
+ );
1600
+ const designDefinitionsBody = readPromptFile(
1601
+ BRIEF_FILL_PROMPT_FILES.designDefinitions
1602
+ );
1603
+ return `${designFromImageBody}${DESIGN_FILL_SUFFIX}
1604
+
1605
+ ---
1606
+
1607
+ ## Reference — design definitions board (Mode A)
1608
+
1609
+ When the image is a formal Figma **design definitions** board, apply these rules in full (in addition to Mode A above):
1610
+
1611
+ ${designDefinitionsBody}`;
1612
+ });
1613
+ }
1614
+ const BRIEF_FILL_MODEL = process.env.AIDITOR_BRIEF_FILL_MODEL ?? "claude-sonnet-4-20250514";
1615
+ const ALLOWED_IMAGE_MIMES = /* @__PURE__ */ new Set([
1616
+ "image/png",
1617
+ "image/jpeg",
1618
+ "image/jpg",
1619
+ "image/webp",
1620
+ "image/gif"
1621
+ ]);
1622
+ function stripMarkdownFences(text) {
1623
+ const t = text.trim();
1624
+ const fenceMatch = /^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i.exec(t);
1625
+ if (fenceMatch) return fenceMatch[1].trim();
1626
+ return t;
1627
+ }
1628
+ const SUGGESTED_ROUTE_RE = /(?:Suggested route|Route)[^:\n]*:+\s*(?:\*\*\s*)?(?:`(\/[^`]+)`|(\/[^\s(]+(?:\[[^\]]+\])?))\s*(?:\((static|dynamic)\))?/i;
1629
+ function parseSuggestedRoute(raw) {
1630
+ const stripped = stripMarkdownFences(raw);
1631
+ const lines = stripped.split("\n");
1632
+ let suggestedRoute;
1633
+ let suggestedRouteKind;
1634
+ const bodyLines = [];
1635
+ for (const line of lines) {
1636
+ const m = SUGGESTED_ROUTE_RE.exec(line);
1637
+ if (m && !suggestedRoute) {
1638
+ suggestedRoute = (m[1] ?? m[2] ?? "").replace(/\/+$/, "") || "/";
1639
+ if (m[3] === "static" || m[3] === "dynamic") {
1640
+ suggestedRouteKind = m[3];
1641
+ }
1642
+ continue;
1643
+ }
1644
+ bodyLines.push(line);
1645
+ }
1646
+ let markdown = bodyLines.join("\n").trim();
1647
+ markdown = markdown.replace(/^---\s*\n+/, "").trim();
1648
+ return { markdown, suggestedRoute, suggestedRouteKind };
1649
+ }
1650
+ function extractImageFilesFromExtraFiles(extraFiles) {
1651
+ if (!extraFiles) return [];
1652
+ const indexed = [];
1653
+ for (const [key, file] of Object.entries(extraFiles)) {
1654
+ const m = /^image_(\d+)$/.exec(key);
1655
+ if (!m || !file) continue;
1656
+ indexed.push({ index: Number.parseInt(m[1], 10), file });
1657
+ }
1658
+ indexed.sort((a, b) => a.index - b.index);
1659
+ return indexed.map((e) => e.file);
1660
+ }
1661
+ function normalizeImageMime(mime, filename) {
1662
+ const t = mime.toLowerCase().split(";")[0].trim();
1663
+ if (ALLOWED_IMAGE_MIMES.has(t)) {
1664
+ return t === "image/jpg" ? "image/jpeg" : t;
1665
+ }
1666
+ const ext = filename.toLowerCase().split(".").pop() ?? "";
1667
+ if (ext === "png") return "image/png";
1668
+ if (ext === "jpg" || ext === "jpeg") return "image/jpeg";
1669
+ if (ext === "webp") return "image/webp";
1670
+ if (ext === "gif") return "image/gif";
1671
+ return t;
1672
+ }
1673
+ function validateBriefFillImages(files) {
1674
+ if (files.length === 0) {
1675
+ return "Add at least one image before generating.";
1676
+ }
1677
+ if (files.length > BRIEF_FILL_MAX_IMAGES) {
1678
+ return `Maximum ${BRIEF_FILL_MAX_IMAGES} images per request.`;
1679
+ }
1680
+ for (const f of files) {
1681
+ const mime = normalizeImageMime(f.type ?? "", f.name);
1682
+ if (!ALLOWED_IMAGE_MIMES.has(mime) && !mime.startsWith("image/")) {
1683
+ return `Unsupported file type: ${f.name}. Use PNG, JPEG, WebP, or GIF.`;
1684
+ }
1685
+ }
1686
+ return null;
1687
+ }
1688
+ function readImageAsBase64(file) {
1689
+ const buf = fs$1.readFileSync(file.path);
1690
+ const mimeType = normalizeImageMime(file.type ?? "", file.name);
1691
+ return {
1692
+ name: file.name,
1693
+ mimeType,
1694
+ base64: buf.toString("base64")
1695
+ };
1696
+ }
1697
+ function buildBriefFillUserMessage(target, images, contextNotes, pageRoute) {
1698
+ const parts = [];
1699
+ parts.push(
1700
+ target === "content" ? "Generate the Content brief markdown for AIditor Add Page from the attached screenshot(s)." : "Generate the Design brief markdown for AIditor Add Page from the attached screenshot(s)."
1701
+ );
1702
+ if (pageRoute?.trim()) {
1703
+ parts.push(`User already typed page route: ${pageRoute.trim()}`);
1704
+ }
1705
+ if (contextNotes?.trim()) {
1706
+ parts.push("", "User context notes:", contextNotes.trim());
1707
+ }
1708
+ parts.push("", `Attached images: ${images.length}`);
1709
+ for (let i = 0; i < images.length; i++) {
1710
+ parts.push(`- Image ${i + 1}: ${images[i].name}`);
1711
+ }
1712
+ return parts.join("\n");
1713
+ }
1714
+ function buildBriefFillMessageContent(images, userText) {
1715
+ return [
1716
+ ...images.flatMap((img, i) => [
1717
+ { type: "text", text: `[Image ${i + 1}: ${img.name}]` },
1718
+ {
1719
+ type: "image",
1720
+ source: {
1721
+ type: "base64",
1722
+ media_type: img.mimeType,
1723
+ data: img.base64
1724
+ }
1725
+ }
1726
+ ]),
1727
+ { type: "text", text: userText }
1728
+ ];
1729
+ }
1730
+ async function* briefFillPromptStream(userText, images) {
1731
+ yield {
1732
+ type: "user",
1733
+ message: {
1734
+ role: "user",
1735
+ content: buildBriefFillMessageContent(images, userText)
1736
+ },
1737
+ parent_tool_use_id: null
1738
+ };
1739
+ }
1740
+ function extractBriefFillRawText(messages) {
1741
+ let rawText = "";
1742
+ for (const message of messages) {
1743
+ if (message.type === "assistant" && Array.isArray(message.message?.content)) {
1744
+ for (const block of message.message.content) {
1745
+ if (block.type === "text" && block.text?.trim()) {
1746
+ rawText = block.text.trim();
1747
+ }
1748
+ }
1749
+ }
1750
+ if (message.type === "result" && message.subtype === "success" && message.result?.trim() && !rawText) {
1751
+ rawText = message.result.trim();
1752
+ }
1753
+ if (message.type === "result" && message.is_error) {
1754
+ const detail = message.errors?.join("; ") || "Brief generation failed. Try again.";
1755
+ throw new Error(detail);
1756
+ }
1757
+ }
1758
+ return rawText;
1759
+ }
1760
+ async function runBriefFillAgentQuery(input) {
1761
+ const systemPrompt = loadBriefFillSystemPrompt(input.target);
1762
+ const userText = buildBriefFillUserMessage(
1763
+ input.target,
1764
+ input.images,
1765
+ input.contextNotes,
1766
+ input.pageRoute
1767
+ );
1768
+ const collected = [];
1769
+ for await (const message of query({
1770
+ prompt: briefFillPromptStream(userText, input.images),
1771
+ options: {
1772
+ cwd: input.projectDir,
1773
+ tools: [],
1774
+ maxTurns: BRIEF_FILL_MAX_TURNS,
1775
+ persistSession: false,
1776
+ systemPrompt,
1777
+ model: BRIEF_FILL_MODEL,
1778
+ thinking: { type: "disabled" }
1779
+ }
1780
+ })) {
1781
+ collected.push(message);
1782
+ }
1783
+ const rawText = extractBriefFillRawText(collected);
1784
+ if (!rawText) {
1785
+ throw new Error(
1786
+ "AI returned an empty brief. Try again with a clearer image."
1787
+ );
1788
+ }
1789
+ return rawText;
1790
+ }
1791
+ async function generateBriefFromImages(input, options) {
1792
+ const rawText = await runBriefFillAgentQuery({
1793
+ projectDir: options.projectDir,
1794
+ ...input
1795
+ });
1796
+ if (input.target === "content") {
1797
+ const parsed = parseSuggestedRoute(rawText);
1798
+ if (!parsed.markdown.trim()) {
1799
+ throw new Error("AI returned an empty Content brief.");
1800
+ }
1801
+ return parsed;
1802
+ }
1803
+ const markdown = stripMarkdownFences(rawText);
1804
+ if (!markdown.trim()) {
1805
+ throw new Error("AI returned an empty Design brief.");
1806
+ }
1807
+ return { markdown };
1808
+ }
1809
+ function persistUpload(file, destPath) {
1810
+ fs$1.mkdirSync(path.dirname(destPath), { recursive: true });
1811
+ fs$1.copyFileSync(file.path, destPath);
1812
+ }
1813
+ const checkAddPageRouteAction = makeJayQuery("aiditor.checkAddPageRoute").withServices(DEV_SERVER_SERVICE).withHandler(async (input, devServer) => {
1814
+ const syntax = validatePageRouteSyntax(input.pageRoute);
1815
+ if (!syntax.valid) {
1816
+ return {
1817
+ normalizedRoute: "",
1818
+ routeKind: "static",
1819
+ routeExists: false,
1820
+ valid: false,
1821
+ error: syntax.error
1822
+ };
1823
+ }
1824
+ const normalizedRoute = normalizePageRoute(input.pageRoute);
1825
+ const routeKind = inferRouteKind(normalizedRoute);
1826
+ const routeExists = routeExistsInProject(
1827
+ normalizedRoute,
1828
+ devServer.listRoutes()
1829
+ );
1830
+ return {
1831
+ normalizedRoute,
1832
+ routeKind,
1833
+ routeExists,
1834
+ valid: true
1835
+ };
1836
+ });
1837
+ const startAddPageRequestAction = makeJayQuery(
1838
+ "aiditor.startAddPageRequest"
1839
+ ).withServices(DEV_SERVER_SERVICE).withHandler(
1840
+ async (input, devServer) => {
1841
+ const syntax = validatePageRouteSyntax(input.pageRoute);
1842
+ if (!syntax.valid) {
1843
+ return {
1844
+ requestId: "",
1845
+ requestDir: "",
1846
+ routeExists: false,
1847
+ routeExistsMessage: syntax.error
1848
+ };
1849
+ }
1850
+ const normalizedRoute = normalizePageRoute(input.pageRoute);
1851
+ const routeKind = inferRouteKind(normalizedRoute);
1852
+ const routeExists = routeExistsInProject(
1853
+ normalizedRoute,
1854
+ devServer.listRoutes()
1855
+ );
1856
+ if (routeExists) {
1857
+ return {
1858
+ requestId: "",
1859
+ requestDir: "",
1860
+ routeExists: true,
1861
+ routeExistsMessage: "This route already exists."
1862
+ };
1863
+ }
1864
+ const projectDir = process.cwd();
1865
+ const { requestDir, request } = await createPageRequest(projectDir, {
1866
+ pageRoute: normalizedRoute,
1867
+ routeKind
1868
+ });
1869
+ return {
1870
+ requestId: request.requestId,
1871
+ requestDir,
1872
+ routeExists: false
1873
+ };
1874
+ }
1875
+ );
1876
+ const saveAddPageDraftAction = makeJayQuery(
1877
+ "aiditor.saveAddPageDraft"
1878
+ ).withHandler(
1879
+ async (input) => {
1880
+ const projectDir = process.cwd();
1881
+ const request = await readPageRequest(projectDir, input.requestId);
1882
+ if (!request) {
1883
+ return { ok: false };
1884
+ }
1885
+ await writePageRequestMarkdown(projectDir, input.requestId, {
1886
+ contentMd: input.contentMd,
1887
+ designMd: input.designMd
1888
+ });
1889
+ return { ok: true };
1890
+ }
1891
+ );
1892
+ const uploadAddPageAssetAction = makeJayQuery(
1893
+ "aiditor.uploadAddPageAsset"
1894
+ ).withMethod("POST").withFiles().withHandler(
1895
+ async (input) => {
1896
+ if (!input.file) {
1897
+ throw new Error("No file uploaded.");
1898
+ }
1899
+ const projectDir = process.cwd();
1900
+ const request = await readPageRequest(projectDir, input.requestId);
1901
+ if (!request) {
1902
+ throw new Error(`Page request not found: ${input.requestId}`);
1903
+ }
1904
+ const id = generatePageRequestId();
1905
+ const filename = sanitizeAttachmentFilename(input.file.name);
1906
+ const subdir = input.role === "asset" ? "assets" : "references";
1907
+ const relPath = `${subdir}/${filename}`;
1908
+ const destPath = path.join(
1909
+ getPageRequestDir(projectDir, input.requestId),
1910
+ relPath
1911
+ );
1912
+ persistUpload(input.file, destPath);
1913
+ if (input.role === "asset") {
1914
+ await addPageRequestAttachment(
1915
+ projectDir,
1916
+ input.requestId,
1917
+ { id, path: relPath, markdownRef: relPath },
1918
+ "asset"
1919
+ );
1920
+ const base = path.basename(filename, path.extname(filename));
1921
+ return {
1922
+ id,
1923
+ path: relPath,
1924
+ markdownRef: relPath,
1925
+ markdownSnippet: `![${base}](${relPath})`
1926
+ };
1927
+ }
1928
+ await addPageRequestAttachment(
1929
+ projectDir,
1930
+ input.requestId,
1931
+ { id, path: relPath, label: filename },
1932
+ "reference"
1933
+ );
1934
+ return {
1935
+ id,
1936
+ path: relPath,
1937
+ markdownRef: relPath
1938
+ };
1939
+ }
1940
+ );
1941
+ const reclassifyAddPageAssetAction = makeJayQuery(
1942
+ "aiditor.reclassifyAddPageAsset"
1943
+ ).withHandler(
1944
+ async (input) => {
1945
+ const projectDir = process.cwd();
1946
+ const updated = await reclassifyPageRequestAttachment(
1947
+ projectDir,
1948
+ input.requestId,
1949
+ input.attachmentId,
1950
+ input.newRole
1951
+ );
1952
+ const entry = input.newRole === "asset" ? updated.assets.find((a) => a.id === input.attachmentId) : updated.references.find((r) => r.id === input.attachmentId);
1953
+ return {
1954
+ ok: true,
1955
+ path: entry?.path ?? "",
1956
+ markdownRef: entry && "markdownRef" in entry ? entry.markdownRef : entry?.path
1957
+ };
1958
+ }
1959
+ );
1960
+ const checkPageAddPageBriefAction = makeJayQuery(
1961
+ "aiditor.checkPageAddPageBrief"
1962
+ ).withHandler(async (input) => {
1963
+ const projectDir = process.cwd();
1964
+ if (await pageBriefExists(projectDir, input.jayHtmlPath)) {
1965
+ return { hasBrief: true };
1966
+ }
1967
+ if (input.pageRoute?.trim()) {
1968
+ const backfilled = await tryBackfillPageBriefForRoute(
1969
+ projectDir,
1970
+ input.jayHtmlPath,
1971
+ input.pageRoute
1972
+ );
1973
+ if (backfilled) {
1974
+ return { hasBrief: true };
1975
+ }
1976
+ }
1977
+ return { hasBrief: false };
1978
+ });
1979
+ const openAddPageFromBriefAction = makeJayQuery(
1980
+ "aiditor.openAddPageFromBrief"
1981
+ ).withServices(DEV_SERVER_SERVICE).withHandler(
1982
+ async (input, devServer) => {
1983
+ const projectDir = process.cwd();
1984
+ const syntax = validatePageRouteSyntax(input.sourcePageRoute);
1985
+ if (!syntax.valid) {
1986
+ return { ok: false, error: syntax.error ?? "Invalid source route." };
1987
+ }
1988
+ const sourceRoute = normalizePageRoute(input.sourcePageRoute);
1989
+ const copyRoute = suggestCopyRoute(sourceRoute, devServer.listRoutes());
1990
+ const copySyntax = validatePageRouteSyntax(copyRoute);
1991
+ if (!copySyntax.valid) {
1992
+ return {
1993
+ ok: false,
1994
+ error: copySyntax.error ?? "Could not derive a copy route."
1995
+ };
1996
+ }
1997
+ if (routeExistsInProject(copyRoute, devServer.listRoutes())) {
1998
+ return {
1999
+ ok: false,
2000
+ error: "Could not find a free route for the page copy."
2001
+ };
2002
+ }
2003
+ try {
2004
+ const cloned = await clonePageBriefToRequest(
2005
+ projectDir,
2006
+ input.jayHtmlPath,
2007
+ { pageRoute: copyRoute }
2008
+ );
2009
+ return {
2010
+ ok: true,
2011
+ requestId: cloned.requestId,
2012
+ pageRoute: copyRoute,
2013
+ routeKind: inferRouteKind(copyRoute),
2014
+ contentMd: cloned.contentMd,
2015
+ designMd: cloned.designMd,
2016
+ attachments: cloned.attachments
2017
+ };
2018
+ } catch (err) {
2019
+ return {
2020
+ ok: false,
2021
+ error: err instanceof Error ? err.message : String(err)
2022
+ };
2023
+ }
2024
+ }
2025
+ );
2026
+ const submitAddPageAction = makeJayStream("aiditor.submitAddPage").withServices(DEV_SERVER_SERVICE).withHandler(async function* (input, devServer) {
2027
+ const projectDir = process.cwd();
2028
+ const syntax = validatePageRouteSyntax(input.pageRoute);
2029
+ if (!syntax.valid) {
2030
+ yield { type: "error", message: syntax.error ?? "Invalid route." };
2031
+ yield { type: "done" };
2032
+ return;
2033
+ }
2034
+ const normalizedRoute = normalizePageRoute(input.pageRoute);
2035
+ const routeKind = inferRouteKind(normalizedRoute);
2036
+ if (routeExistsInProject(normalizedRoute, devServer.listRoutes()) && !input.isRetry) {
2037
+ yield {
2038
+ type: "error",
2039
+ message: "This route already exists."
2040
+ };
2041
+ yield { type: "done" };
2042
+ return;
2043
+ }
2044
+ const request = await readPageRequest(projectDir, input.requestId);
2045
+ if (!request) {
2046
+ yield { type: "error", message: "Page request not found." };
2047
+ yield { type: "done" };
2048
+ return;
2049
+ }
2050
+ await writePageRequestMarkdown(projectDir, input.requestId, {
2051
+ contentMd: input.contentMd,
2052
+ designMd: input.designMd
2053
+ });
2054
+ await updatePageRequestStatus(projectDir, input.requestId, "running");
2055
+ const requestDir = getPageRequestDir(projectDir, input.requestId);
2056
+ yield { type: "status", message: "Running Add Page agent…" };
2057
+ let agentFailed = false;
2058
+ try {
2059
+ for await (const chunk of streamAddPageAgentQuery({
2060
+ projectDir,
2061
+ requestDir,
2062
+ pageRoute: normalizedRoute,
2063
+ routeKind,
2064
+ isRetry: input.isRetry,
2065
+ selectedPlugins: request.selectedPlugins
2066
+ })) {
2067
+ yield chunk;
2068
+ if (chunk.type === "error") {
2069
+ agentFailed = true;
2070
+ }
2071
+ }
2072
+ } catch (err) {
2073
+ agentFailed = true;
2074
+ yield {
2075
+ type: "error",
2076
+ message: err instanceof Error ? err.message : String(err)
2077
+ };
2078
+ }
2079
+ if (agentFailed) {
2080
+ await updatePageRequestStatus(projectDir, input.requestId, "failed");
2081
+ yield { type: "done" };
2082
+ return;
2083
+ }
2084
+ yield { type: "status", message: "Running jay-stack validate…" };
2085
+ const validateResult = await runAddPageValidate(projectDir);
2086
+ if (!validateResult.ok) {
2087
+ await updatePageRequestStatus(projectDir, input.requestId, "failed");
2088
+ yield {
2089
+ type: "error",
2090
+ message: "Validation failed.",
2091
+ text: validateResult.errors.join("\n"),
2092
+ validateErrors: validateResult.errors
2093
+ };
2094
+ yield { type: "done" };
2095
+ return;
2096
+ }
2097
+ await updatePageRequestStatus(projectDir, input.requestId, "completed");
2098
+ await devServer.refreshRoutes();
2099
+ const jayHtmlPath = await resolveJayHtmlPathForPageRoute(
2100
+ projectDir,
2101
+ normalizedRoute,
2102
+ devServer.listRoutes()
2103
+ );
2104
+ if (jayHtmlPath) {
2105
+ try {
2106
+ await savePageBriefFromRequest(projectDir, jayHtmlPath, {
2107
+ requestId: input.requestId,
2108
+ pageRoute: normalizedRoute,
2109
+ contentMd: input.contentMd,
2110
+ designMd: input.designMd
2111
+ });
2112
+ } catch (err) {
2113
+ getLogger().warn(
2114
+ `[Add Page] Failed to save page brief for ${normalizedRoute}: ${err instanceof Error ? err.message : String(err)}`
2115
+ );
2116
+ }
2117
+ }
2118
+ yield {
2119
+ type: "result",
2120
+ message: `Page created at ${normalizedRoute}.`,
2121
+ text: normalizedRoute
2122
+ };
2123
+ yield { type: "done" };
2124
+ });
2125
+ const generateAddPageBriefFromImageAction = makeJayQuery(
2126
+ "aiditor.generateAddPageBriefFromImage"
2127
+ ).withMethod("POST").withFiles().withHandler(
2128
+ async (input) => {
2129
+ const imageFiles = extractImageFilesFromExtraFiles(input.extraFiles);
2130
+ const validationError = validateBriefFillImages(imageFiles);
2131
+ if (validationError) {
2132
+ return { ok: false, error: validationError };
2133
+ }
2134
+ const projectDir = process.cwd();
2135
+ if (input.requestId) {
2136
+ const request = await readPageRequest(projectDir, input.requestId);
2137
+ if (!request) {
2138
+ return {
2139
+ ok: false,
2140
+ error: `Page request not found: ${input.requestId}`
2141
+ };
2142
+ }
2143
+ }
2144
+ try {
2145
+ const images = imageFiles.map(readImageAsBase64);
2146
+ const result = await generateBriefFromImages(
2147
+ {
2148
+ target: input.target,
2149
+ images,
2150
+ contextNotes: input.contextNotes,
2151
+ pageRoute: input.pageRoute
2152
+ },
2153
+ { projectDir }
2154
+ );
2155
+ const referenceAttachments = [];
2156
+ if (input.requestId) {
2157
+ for (const file of imageFiles) {
2158
+ const id = generatePageRequestId();
2159
+ const filename = sanitizeAttachmentFilename(file.name);
2160
+ const relPath = `references/${filename}`;
2161
+ const destPath = path.join(
2162
+ getPageRequestDir(projectDir, input.requestId),
2163
+ relPath
2164
+ );
2165
+ persistUpload(file, destPath);
2166
+ await addPageRequestAttachment(
2167
+ projectDir,
2168
+ input.requestId,
2169
+ { id, path: relPath, label: filename },
2170
+ "reference"
2171
+ );
2172
+ referenceAttachments.push({ id, path: relPath, filename });
2173
+ }
2174
+ }
2175
+ return {
2176
+ ok: true,
2177
+ markdown: result.markdown,
2178
+ suggestedRoute: result.suggestedRoute,
2179
+ suggestedRouteKind: result.suggestedRouteKind,
2180
+ referenceAttachments
2181
+ };
2182
+ } catch (err) {
2183
+ return {
2184
+ ok: false,
2185
+ error: err instanceof Error ? err.message : String(err)
2186
+ };
2187
+ }
2188
+ }
2189
+ );
2190
+ function parsePluginsIndexYaml(yaml) {
2191
+ const entries = [];
2192
+ let currentPlugin = null;
2193
+ let inContracts = false;
2194
+ for (const line of yaml.split("\n")) {
2195
+ const pluginMatch = line.match(/^ - name:\s*(.+)$/);
2196
+ if (pluginMatch && !line.startsWith(" ")) {
2197
+ currentPlugin = pluginMatch[1].trim();
2198
+ inContracts = false;
2199
+ continue;
2200
+ }
2201
+ if (line.trim() === "contracts:") {
2202
+ inContracts = true;
2203
+ continue;
2204
+ }
2205
+ if (inContracts && currentPlugin) {
2206
+ const contractMatch = line.match(/^ - name:\s*(.+)$/);
2207
+ if (contractMatch) {
2208
+ entries.push({
2209
+ pluginName: currentPlugin,
2210
+ contractName: contractMatch[1].trim()
2211
+ });
2212
+ continue;
2213
+ }
2214
+ const descMatch = line.match(/^ description:\s*(.+)$/);
2215
+ if (descMatch && entries.length > 0) {
2216
+ const last = entries[entries.length - 1];
2217
+ if (last.pluginName === currentPlugin) {
2218
+ last.description = descMatch[1].trim();
2219
+ }
2220
+ continue;
2221
+ }
2222
+ const typeMatch = line.match(/^ type:\s*(.+)$/);
2223
+ if (typeMatch && entries.length > 0) {
2224
+ const last = entries[entries.length - 1];
2225
+ if (last.pluginName === currentPlugin) {
2226
+ last.isDynamic = typeMatch[1].trim() === "dynamic";
2227
+ }
2228
+ }
2229
+ }
2230
+ if (line.match(/^ actions:/) || line.match(/^ routes:/)) {
2231
+ inContracts = false;
2232
+ }
2233
+ }
2234
+ return entries;
2235
+ }
2236
+ function packageNameToPluginName(packageName) {
2237
+ const prefix = "@jay-framework/";
2238
+ if (packageName.startsWith(prefix)) {
2239
+ return packageName.slice(prefix.length);
2240
+ }
2241
+ return packageName;
2242
+ }
2243
+ async function readPackageJsonDeps(projectRoot) {
2244
+ const raw = await fs.readFile(
2245
+ path.join(projectRoot, "package.json"),
2246
+ "utf-8"
2247
+ );
2248
+ const pkg = JSON.parse(raw);
2249
+ return { ...pkg.devDependencies, ...pkg.dependencies };
2250
+ }
2251
+ async function getInstalledPlugins(projectRoot) {
2252
+ const deps = await readPackageJsonDeps(projectRoot);
2253
+ const installed = [];
2254
+ for (const [packageName, spec] of Object.entries(deps)) {
2255
+ if (!packageName.startsWith("@jay-framework/")) continue;
2256
+ installed.push({
2257
+ pluginName: packageNameToPluginName(packageName),
2258
+ packageName,
2259
+ installSpec: spec
2260
+ });
2261
+ }
2262
+ return installed;
2263
+ }
2264
+ async function isPluginInstalled(projectRoot, pluginName) {
2265
+ const installed = await getInstalledPlugins(projectRoot);
2266
+ return installed.some((p) => p.pluginName === pluginName);
2267
+ }
2268
+ async function parsePluginsIndexContracts(projectRoot) {
2269
+ const indexPath = path.join(projectRoot, "agent-kit", "plugins-index.yaml");
2270
+ try {
2271
+ const yaml = await fs.readFile(indexPath, "utf-8");
2272
+ return parsePluginsIndexYaml(yaml);
2273
+ } catch (e) {
2274
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
2275
+ if (code === "ENOENT") return [];
2276
+ throw e;
2277
+ }
2278
+ }
2279
+ async function parsePluginYamlContracts(projectRoot, pluginName) {
2280
+ const pluginYamlPath = path.join(
2281
+ projectRoot,
2282
+ "node_modules",
2283
+ "@jay-framework",
2284
+ pluginName,
2285
+ "plugin.yaml"
2286
+ );
2287
+ try {
2288
+ const yaml = await fs.readFile(pluginYamlPath, "utf-8");
2289
+ const entries = [];
2290
+ let inContracts = false;
2291
+ for (const line of yaml.split("\n")) {
2292
+ if (line.trim() === "contracts:") {
2293
+ inContracts = true;
2294
+ continue;
2295
+ }
2296
+ if (inContracts) {
2297
+ const nameMatch = line.match(/^ - name:\s*(.+)$/);
2298
+ if (nameMatch) {
2299
+ entries.push({
2300
+ pluginName,
2301
+ contractName: nameMatch[1].trim()
2302
+ });
2303
+ }
2304
+ if (line.match(/^actions:/) || line.match(/^routes:/)) {
2305
+ break;
2306
+ }
2307
+ }
2308
+ }
2309
+ return entries;
2310
+ } catch {
2311
+ return [];
2312
+ }
2313
+ }
2314
+ const LOCAL_SPEC_PREFIXES = ["portal:", "file:", "workspace:"];
2315
+ function isRegistryInstallSpec(installSpec) {
2316
+ if (!installSpec) return true;
2317
+ return !LOCAL_SPEC_PREFIXES.some((p) => installSpec.startsWith(p));
2318
+ }
2319
+ const VALID_INSTALL_PREFIXES = ["portal:", "file:", "workspace:"];
2320
+ function stripYamlScalar(value) {
2321
+ return value.replace(/^["']|["']$/g, "");
2322
+ }
2323
+ function isValidInstallSpec(spec) {
2324
+ return VALID_INSTALL_PREFIXES.some((p) => spec.startsWith(p));
2325
+ }
2326
+ function getPluginSourcesPath(projectRoot) {
2327
+ return path.join(projectRoot, ".aiditor", "plugin-sources.yaml");
2328
+ }
2329
+ function parsePluginSourcesYaml(yaml) {
2330
+ const sources = [];
2331
+ let current = null;
2332
+ for (const line of yaml.split("\n")) {
2333
+ const itemMatch = line.match(/^ - pluginName:\s*(.+)$/);
2334
+ if (itemMatch) {
2335
+ if (current?.pluginName && current.packageName && current.installSpec) {
2336
+ sources.push(current);
2337
+ }
2338
+ current = { pluginName: itemMatch[1].trim() };
2339
+ continue;
2340
+ }
2341
+ if (!current) continue;
2342
+ const pkgMatch = line.match(/^ packageName:\s*(.+)$/);
2343
+ if (pkgMatch) {
2344
+ current.packageName = stripYamlScalar(pkgMatch[1].trim());
2345
+ continue;
2346
+ }
2347
+ const specMatch = line.match(/^ installSpec:\s*(.+)$/);
2348
+ if (specMatch) {
2349
+ current.installSpec = stripYamlScalar(specMatch[1].trim());
2350
+ continue;
2351
+ }
2352
+ const labelMatch = line.match(/^ label:\s*(.+)$/);
2353
+ if (labelMatch) {
2354
+ current.label = stripYamlScalar(labelMatch[1].trim());
2355
+ continue;
2356
+ }
2357
+ const kindMatch = line.match(/^ kind:\s*(.+)$/);
2358
+ if (kindMatch) {
2359
+ current.kind = kindMatch[1].trim();
2360
+ continue;
2361
+ }
2362
+ const descMatch = line.match(/^ description:\s*(.+)$/);
2363
+ if (descMatch) {
2364
+ current.description = descMatch[1].trim();
2365
+ }
2366
+ }
2367
+ if (current?.pluginName && current.packageName && current.installSpec) {
2368
+ sources.push(current);
2369
+ }
2370
+ return { sources };
2371
+ }
2372
+ function serializePluginSources(file) {
2373
+ const lines = ["# Unpublished or monorepo-local plugins", "sources:"];
2374
+ for (const s of file.sources) {
2375
+ lines.push(` - pluginName: ${s.pluginName}`);
2376
+ lines.push(` packageName: ${s.packageName}`);
2377
+ lines.push(` installSpec: "${s.installSpec}"`);
2378
+ if (s.label) lines.push(` label: ${s.label}`);
2379
+ if (s.kind) lines.push(` kind: ${s.kind}`);
2380
+ if (s.description) lines.push(` description: ${s.description}`);
2381
+ }
2382
+ return `${lines.join("\n")}
2383
+ `;
2384
+ }
2385
+ async function readPluginSources(projectRoot) {
2386
+ const filePath = getPluginSourcesPath(projectRoot);
2387
+ try {
2388
+ const raw = await fs.readFile(filePath, "utf-8");
2389
+ return parsePluginSourcesYaml(raw);
2390
+ } catch (e) {
2391
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
2392
+ if (code === "ENOENT") return { sources: [] };
2393
+ throw e;
2394
+ }
2395
+ }
2396
+ async function writePluginSource(projectRoot, entry) {
2397
+ if (!isValidInstallSpec(entry.installSpec)) {
2398
+ throw new Error(
2399
+ `installSpec must start with portal:, file:, or workspace:`
2400
+ );
2401
+ }
2402
+ const file = await readPluginSources(projectRoot);
2403
+ const ix = file.sources.findIndex((s) => s.pluginName === entry.pluginName);
2404
+ if (ix >= 0) {
2405
+ file.sources[ix] = entry;
2406
+ } else {
2407
+ file.sources.push(entry);
2408
+ }
2409
+ const dir = path.join(projectRoot, ".aiditor");
2410
+ await fs.mkdir(dir, { recursive: true });
2411
+ await fs.writeFile(
2412
+ getPluginSourcesPath(projectRoot),
2413
+ serializePluginSources(file),
2414
+ "utf-8"
2415
+ );
2416
+ }
2417
+ function mergeCatalogWithSources(catalog, sources) {
2418
+ const byName = /* @__PURE__ */ new Map();
2419
+ for (const e of catalog) {
2420
+ byName.set(e.pluginName, { ...e, isLocalSource: false });
2421
+ }
2422
+ for (const s of sources) {
2423
+ const existing = byName.get(s.pluginName);
2424
+ byName.set(s.pluginName, {
2425
+ pluginName: s.pluginName,
2426
+ packageName: s.packageName,
2427
+ description: s.description ?? existing?.description ?? s.pluginName,
2428
+ kind: s.kind ?? existing?.kind ?? "headless",
2429
+ requires: existing?.requires ?? [],
2430
+ showInAddPagePicker: existing?.showInAddPagePicker ?? true,
2431
+ configFiles: existing?.configFiles,
2432
+ isLocalSource: true,
2433
+ installSpec: s.installSpec,
2434
+ localLabel: s.label
2435
+ });
2436
+ }
2437
+ return [...byName.values()];
2438
+ }
2439
+ function readPackageJson(dir) {
2440
+ const pkgPath = path.join(dir, "package.json");
2441
+ if (!fs$1.existsSync(pkgPath)) return void 0;
2442
+ try {
2443
+ return JSON.parse(fs$1.readFileSync(pkgPath, "utf-8"));
2444
+ } catch {
2445
+ return void 0;
2446
+ }
2447
+ }
2448
+ function hasYarnWorkspaces(pkg) {
2449
+ if (!pkg?.workspaces) return false;
2450
+ return Array.isArray(pkg.workspaces) || typeof pkg.workspaces === "object" && pkg.workspaces !== null;
2451
+ }
2452
+ function findYarnWorkspaceRoot(startDir) {
2453
+ let dir = path.resolve(startDir);
2454
+ let innermostPkgDir;
2455
+ while (true) {
2456
+ const pkg = readPackageJson(dir);
2457
+ if (pkg) {
2458
+ innermostPkgDir = dir;
2459
+ if (hasYarnWorkspaces(pkg) || pkg.packageManager?.startsWith("yarn@")) {
2460
+ return dir;
2461
+ }
2462
+ }
2463
+ const parent = path.dirname(dir);
2464
+ if (parent === dir) break;
2465
+ dir = parent;
2466
+ }
2467
+ return innermostPkgDir;
2468
+ }
2469
+ function readYarnPathFromRc(workspaceRoot) {
2470
+ const rcPath = path.join(workspaceRoot, ".yarnrc.yml");
2471
+ if (!fs$1.existsSync(rcPath)) return void 0;
2472
+ const content = fs$1.readFileSync(rcPath, "utf-8");
2473
+ const match = content.match(/^yarnPath:\s*["']?([^"'\n]+)["']?\s*$/m);
2474
+ if (!match?.[1]) return void 0;
2475
+ return path.resolve(workspaceRoot, match[1].trim());
2476
+ }
2477
+ function resolveYarnRunner(workspaceRoot) {
2478
+ const pkg = readPackageJson(workspaceRoot);
2479
+ const yarnPath = readYarnPathFromRc(workspaceRoot);
2480
+ if (yarnPath && fs$1.existsSync(yarnPath)) {
2481
+ return { command: "node", prefixArgs: [yarnPath] };
2482
+ }
2483
+ if (pkg?.packageManager?.startsWith("yarn@")) {
2484
+ return { command: "corepack", prefixArgs: ["yarn"] };
2485
+ }
2486
+ return { command: "yarn", prefixArgs: [] };
2487
+ }
2488
+ function formatYarnInstallError(raw) {
2489
+ const text = raw.trim();
2490
+ if (!text) {
2491
+ return "Package install failed (Yarn produced no output).";
2492
+ }
2493
+ const lower = text.toLowerCase();
2494
+ if (lower.includes("reading 'manifest'") || lower.includes("missing version in workspace") || lower.includes("yarn add v1.")) {
2495
+ return [
2496
+ "Wrong Yarn version: use Yarn 4 from the monorepo root, not global Yarn 1.x.",
2497
+ "From jay-aiditor root: corepack yarn workspace aiditor-starter add <package>.",
2498
+ "Or click Fix with agent to let Claude install the plugin."
2499
+ ].join(" ");
2500
+ }
2501
+ if (lower.includes("yn0027") || lower.includes("@unknown")) {
2502
+ return [
2503
+ "Yarn could not resolve a version (often because the registry request failed).",
2504
+ "Check network/VPN, retry Install, or use Fix with agent / Add local plugin (portal:)."
2505
+ ].join(" ");
2506
+ }
2507
+ if (lower.includes("requesterror") || lower.includes("ebadf") || lower.includes("enotfound") || lower.includes("econnrefused") || lower.includes("etimedout") || lower.includes("getaddrinfo") || lower.includes("yn0035")) {
2508
+ return [
2509
+ "Could not reach the npm registry (network error during Yarn resolution).",
2510
+ "Check internet, VPN, or corporate proxy, then retry or use Fix with agent.",
2511
+ "For local Wix plugins: Add local plugin → portal:../../../wix/packages/<name>."
2512
+ ].join(" ");
2513
+ }
2514
+ if (lower.includes("isn't listed by your request") || lower.includes("no candidates found") || text.includes("YN0035")) {
2515
+ return [
2516
+ "Package version is not on the npm registry (Wix plugins are published on the 0.16.x line).",
2517
+ "Retry Install, or use “Add local plugin” with portal:../../../wix/packages/<name>."
2518
+ ].join(" ");
2519
+ }
2520
+ const lines = text.split("\n").map((l) => l.trim()).filter(
2521
+ (l) => l.length > 0 && !l.includes("/.cache/node/corepack") && !l.includes("yarn.js:")
2522
+ );
2523
+ const summary = lines.slice(0, 8).join("\n");
2524
+ return summary.length > 600 ? `${summary.slice(0, 600)}…` : summary;
2525
+ }
2526
+ function resolveYarnAddInvocation(projectDir, spec) {
2527
+ const resolvedProject = path.resolve(projectDir);
2528
+ const workspaceRoot = findYarnWorkspaceRoot(resolvedProject);
2529
+ const root = workspaceRoot ?? resolvedProject;
2530
+ const runner = resolveYarnRunner(root);
2531
+ const projectPkg = readPackageJson(resolvedProject);
2532
+ const rootPkg = readPackageJson(root);
2533
+ const useWorkspaceCommand = workspaceRoot !== void 0 && path.resolve(root) !== resolvedProject && hasYarnWorkspaces(rootPkg) && typeof projectPkg?.name === "string" && projectPkg.name.length > 0;
2534
+ if (useWorkspaceCommand) {
2535
+ return {
2536
+ command: runner.command,
2537
+ args: [...runner.prefixArgs, "workspace", projectPkg.name, "add", spec],
2538
+ cwd: root
2539
+ };
2540
+ }
2541
+ const args = runner.prefixArgs.length > 0 ? [...runner.prefixArgs, "add", spec] : ["add", spec];
2542
+ return {
2543
+ command: runner.command,
2544
+ args,
2545
+ cwd: resolvedProject
2546
+ };
2547
+ }
2548
+ function parseSemver(version) {
2549
+ const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
2550
+ if (!m) return null;
2551
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
2552
+ }
2553
+ function compareSemver(a, b) {
2554
+ for (let i = 0; i < 3; i++) {
2555
+ const diff = (a[i] ?? 0) - (b[i] ?? 0);
2556
+ if (diff !== 0) return diff;
2557
+ }
2558
+ return 0;
2559
+ }
2560
+ function isNewerSemver(latest, installed) {
2561
+ const a = parseSemver(latest);
2562
+ const b = parseSemver(installed);
2563
+ if (!a || !b) return false;
2564
+ return compareSemver(a, b) > 0;
2565
+ }
2566
+ async function spawnCommand(command, args, cwd, options) {
2567
+ return new Promise((resolve) => {
2568
+ const child = spawn(command, args, {
2569
+ cwd,
2570
+ shell: options?.shell ?? false,
2571
+ env: process.env
2572
+ });
2573
+ let stdout = "";
2574
+ let stderr = "";
2575
+ child.stdout.on("data", (chunk) => {
2576
+ stdout += chunk.toString();
2577
+ });
2578
+ child.stderr.on("data", (chunk) => {
2579
+ stderr += chunk.toString();
2580
+ });
2581
+ child.on("close", (code) => {
2582
+ resolve({ code, stdout, stderr });
2583
+ });
2584
+ child.on("error", (err) => {
2585
+ resolve({ code: 1, stdout, stderr: err.message });
2586
+ });
2587
+ });
2588
+ }
2589
+ async function yarnAdd(projectDir, spec) {
2590
+ const inv = resolveYarnAddInvocation(projectDir, spec);
2591
+ return spawnCommand(inv.command, inv.args, inv.cwd);
2592
+ }
2593
+ async function npmAdd(projectDir, packageName, version) {
2594
+ const spec = version ? `${packageName}@${version}` : packageName;
2595
+ return spawnCommand("npm", ["install", spec, "--save"], projectDir);
2596
+ }
2597
+ async function runJayStackSetup(projectDir, pluginName, force) {
2598
+ const args = ["jay-stack-cli", "setup", pluginName];
2599
+ if (force) args.push("--force");
2600
+ return spawnCommand("npx", args, projectDir, { shell: true });
2601
+ }
2602
+ async function runJayStackAgentKit(projectDir, pluginName) {
2603
+ return spawnCommand(
2604
+ "npx",
2605
+ ["jay-stack-cli", "agent-kit", "--plugin", pluginName],
2606
+ projectDir,
2607
+ { shell: true }
2608
+ );
2609
+ }
2610
+ function parseSetupOutput(stdout, stderr, exitCode) {
2611
+ const combined = `${stdout}
2612
+ ${stderr}`.trim();
2613
+ const jsonMatch = combined.match(/\{[\s\S]*"status"[\s\S]*\}/);
2614
+ if (jsonMatch) {
2615
+ try {
2616
+ const parsed = JSON.parse(jsonMatch[0]);
2617
+ if (parsed.status === "configured") {
2618
+ return {
2619
+ setupStatus: "configured",
2620
+ configCreated: parsed.configCreated,
2621
+ message: parsed.message
2622
+ };
2623
+ }
2624
+ if (parsed.status === "needs-config") {
2625
+ return {
2626
+ setupStatus: "needs-config",
2627
+ configCreated: parsed.configCreated,
2628
+ message: parsed.message
2629
+ };
2630
+ }
2631
+ if (parsed.status === "error") {
2632
+ return {
2633
+ setupStatus: "error",
2634
+ message: parsed.message ?? combined
2635
+ };
2636
+ }
2637
+ } catch {
2638
+ }
2639
+ }
2640
+ if (exitCode === 0) {
2641
+ const lower = combined.toLowerCase();
2642
+ if (lower.includes("needs-config") || lower.includes("needs config") || lower.includes("placeholder")) {
2643
+ return {
2644
+ setupStatus: "needs-config",
2645
+ message: combined || "Plugin needs configuration."
2646
+ };
2647
+ }
2648
+ return { setupStatus: "configured", message: combined || void 0 };
2649
+ }
2650
+ return {
2651
+ setupStatus: "error",
2652
+ message: combined || `setup exited with code ${exitCode}`
2653
+ };
2654
+ }
2655
+ async function readInstalledPackageVersion(projectRoot, packageName) {
2656
+ const parts = packageName.split("/");
2657
+ const pkgPath = path.join(
2658
+ projectRoot,
2659
+ "node_modules",
2660
+ ...parts,
2661
+ "package.json"
2662
+ );
2663
+ try {
2664
+ const raw = await fs.readFile(pkgPath, "utf-8");
2665
+ const pkg = JSON.parse(raw);
2666
+ return pkg.version ?? null;
2667
+ } catch {
2668
+ return null;
2669
+ }
2670
+ }
2671
+ async function fetchNpmLatestVersion(packageName) {
2672
+ const result = await spawnCommand(
2673
+ "npm",
2674
+ ["view", packageName, "version", "--json"],
2675
+ process.cwd()
2676
+ );
2677
+ if (result.code !== 0) return null;
2678
+ const out = (result.stdout || result.stderr).trim();
2679
+ if (!out) return null;
2680
+ try {
2681
+ const parsed = JSON.parse(out);
2682
+ return typeof parsed === "string" ? parsed : null;
2683
+ } catch {
2684
+ return out.replace(/^"|"$/g, "").trim() || null;
2685
+ }
2686
+ }
2687
+ async function checkPluginUpdate(projectRoot, pluginName, packageName, installSpec) {
2688
+ const base = {
2689
+ pluginName,
2690
+ packageName,
2691
+ installedVersion: null,
2692
+ latestVersion: null,
2693
+ updateAvailable: false,
2694
+ isLocalSource: false
2695
+ };
2696
+ if (!isRegistryInstallSpec(installSpec)) {
2697
+ return {
2698
+ ...base,
2699
+ isLocalSource: true,
2700
+ message: "Local source — registry updates do not apply."
2701
+ };
2702
+ }
2703
+ const installedVersion = await readInstalledPackageVersion(
2704
+ projectRoot,
2705
+ packageName
2706
+ );
2707
+ const latestVersion = await fetchNpmLatestVersion(packageName);
2708
+ if (!latestVersion) {
2709
+ return {
2710
+ ...base,
2711
+ installedVersion,
2712
+ message: "Could not fetch latest version from npm."
2713
+ };
2714
+ }
2715
+ const updateAvailable = installedVersion != null && isNewerSemver(latestVersion, installedVersion);
2716
+ return {
2717
+ ...base,
2718
+ installedVersion,
2719
+ latestVersion,
2720
+ updateAvailable,
2721
+ message: updateAvailable ? `Update available: ${installedVersion} → ${latestVersion}` : installedVersion ? `Up to date (${installedVersion})` : void 0
2722
+ };
2723
+ }
2724
+ function getSetupCachePath(projectRoot) {
2725
+ return path.join(projectRoot, ".aiditor", "plugin-setup-status.json");
2726
+ }
2727
+ async function readSetupCache(projectRoot) {
2728
+ const filePath = getSetupCachePath(projectRoot);
2729
+ try {
2730
+ const raw = await fs.readFile(filePath, "utf-8");
2731
+ return JSON.parse(raw);
2732
+ } catch (e) {
2733
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
2734
+ if (code === "ENOENT") return {};
2735
+ throw e;
2736
+ }
2737
+ }
2738
+ async function writeSetupCacheEntry(projectRoot, pluginName, entry) {
2739
+ const cache2 = await readSetupCache(projectRoot);
2740
+ cache2[pluginName] = entry;
2741
+ const dir = path.join(projectRoot, ".aiditor");
2742
+ await fs.mkdir(dir, { recursive: true });
2743
+ await fs.writeFile(
2744
+ getSetupCachePath(projectRoot),
2745
+ `${JSON.stringify(cache2, null, 2)}
2746
+ `,
2747
+ "utf-8"
2748
+ );
2749
+ }
2750
+ function getCachedSetupStatus(cache2, pluginName) {
2751
+ return cache2[pluginName];
2752
+ }
2753
+ function topologicalInstallOrder(target, catalog) {
2754
+ const byName = new Map(catalog.map((e) => [e.pluginName, e]));
2755
+ const visited = /* @__PURE__ */ new Set();
2756
+ const order = [];
2757
+ function visit(name) {
2758
+ if (visited.has(name)) return;
2759
+ visited.add(name);
2760
+ const entry = byName.get(name);
2761
+ if (!entry) return;
2762
+ for (const req of entry.requires) visit(req);
2763
+ order.push(entry);
2764
+ }
2765
+ visit(target.pluginName);
2766
+ return order;
2767
+ }
2768
+ function buildRegistryInstallSpec(packageName) {
2769
+ return packageName;
2770
+ }
2771
+ async function resolveInstallSpec(projectRoot, entry, overrideSpec) {
2772
+ if (overrideSpec) return overrideSpec;
2773
+ const sources = await readPluginSources(projectRoot);
2774
+ const local = sources.sources.find((s) => s.pluginName === entry.pluginName);
2775
+ if (local?.installSpec) {
2776
+ return `${entry.packageName}@${local.installSpec}`;
2777
+ }
2778
+ const deps = await readPackageJsonDeps(projectRoot);
2779
+ const existing = deps[entry.packageName];
2780
+ if (existing && !isRegistryInstallSpec(existing)) {
2781
+ return `${entry.packageName}@${existing}`;
2782
+ }
2783
+ const latest = await fetchNpmLatestVersion(entry.packageName);
2784
+ if (latest) {
2785
+ return `${entry.packageName}@${latest}`;
2786
+ }
2787
+ return buildRegistryInstallSpec(entry.packageName);
2788
+ }
2789
+ function isRegistryResolutionFailure(output) {
2790
+ const lower = output.toLowerCase();
2791
+ return lower.includes("requesterror") || lower.includes("ebadf") || lower.includes("yn0027") || lower.includes("yn0035") || lower.includes("@unknown") || lower.includes("enotfound") || lower.includes("econnrefused");
2792
+ }
2793
+ async function installOnePlugin(projectRoot, entry, installSpec) {
2794
+ const spec = await resolveInstallSpec(projectRoot, entry, installSpec);
2795
+ const yarnResult = await yarnAdd(projectRoot, spec);
2796
+ if (yarnResult.code === 0) {
2797
+ return { ok: true };
2798
+ }
2799
+ const yarnOut = `${yarnResult.stderr}
2800
+ ${yarnResult.stdout}`.trim();
2801
+ const isRegistryPackageSpec = spec === entry.packageName || spec.startsWith(`${entry.packageName}@`);
2802
+ const useNpmFallback = isRegistryPackageSpec && isRegistryResolutionFailure(yarnOut);
2803
+ if (useNpmFallback) {
2804
+ const latest = await fetchNpmLatestVersion(entry.packageName);
2805
+ const npmResult = await npmAdd(
2806
+ projectRoot,
2807
+ entry.packageName,
2808
+ latest ?? void 0
2809
+ );
2810
+ if (npmResult.code === 0) {
2811
+ return { ok: true };
2812
+ }
2813
+ const npmOut = `${npmResult.stderr}
2814
+ ${npmResult.stdout}`.trim();
2815
+ return {
2816
+ ok: false,
2817
+ error: `Yarn failed:
2818
+ ${yarnOut}
2819
+
2820
+ npm fallback failed:
2821
+ ${npmOut}`
2822
+ };
2823
+ }
2824
+ return {
2825
+ ok: false,
2826
+ error: yarnOut || "yarn add failed"
2827
+ };
2828
+ }
2829
+ async function contractsForPlugin$1(projectRoot, pluginName) {
2830
+ const fromIndex = await parsePluginsIndexContracts(projectRoot);
2831
+ const filtered = fromIndex.filter((c) => c.pluginName === pluginName);
2832
+ if (filtered.length > 0) return filtered;
2833
+ return parsePluginYamlContracts(projectRoot, pluginName);
2834
+ }
2835
+ async function* ensureProjectPlugin(projectRoot, pluginName, options) {
2836
+ const catalog = getJayPluginCatalog();
2837
+ const entry = getCatalogEntry(pluginName);
2838
+ if (!entry) {
2839
+ yield { type: "error", message: `Unknown plugin: ${pluginName}` };
2840
+ return;
2841
+ }
2842
+ const order = topologicalInstallOrder(entry, catalog);
2843
+ for (const item of order) {
2844
+ const installed = await isPluginInstalled(projectRoot, item.pluginName);
2845
+ if (installed && item.pluginName !== pluginName) continue;
2846
+ const isTarget = item.pluginName === pluginName;
2847
+ const shouldInstall = !installed || isTarget && options?.upgrade === true;
2848
+ if (shouldInstall) {
2849
+ yield {
2850
+ type: "progress",
2851
+ step: isTarget ? "package" : "requires",
2852
+ message: isTarget ? options?.upgrade ? `Updating ${item.packageName} to latest…` : `Installing ${item.packageName}…` : `Installing dependency ${item.pluginName}…`
2853
+ };
2854
+ const spec = isTarget ? options?.installSpec : void 0;
2855
+ const installResult = await installOnePlugin(projectRoot, item, spec);
2856
+ if (!installResult.ok) {
2857
+ const raw = installResult.error ?? "";
2858
+ yield {
2859
+ type: "error",
2860
+ message: `Could not install ${item.pluginName}.`,
2861
+ installError: formatYarnInstallError(raw)
2862
+ };
2863
+ return;
2864
+ }
2865
+ }
2866
+ }
2867
+ yield {
2868
+ type: "progress",
2869
+ step: "setup",
2870
+ message: `Running setup for ${pluginName}…`
2871
+ };
2872
+ const setupResult = await runJayStackSetup(projectRoot, pluginName);
2873
+ const parsed = parseSetupOutput(
2874
+ setupResult.stdout,
2875
+ setupResult.stderr,
2876
+ setupResult.code
2877
+ );
2878
+ const configFiles = entry.configFiles ?? parsed.configCreated ?? [];
2879
+ await writeSetupCacheEntry(projectRoot, pluginName, {
2880
+ setupStatus: parsed.setupStatus,
2881
+ configFiles,
2882
+ message: parsed.message,
2883
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
2884
+ });
2885
+ yield {
2886
+ type: "progress",
2887
+ step: "agent-kit",
2888
+ message: `Refreshing contracts for ${pluginName}…`
2889
+ };
2890
+ const agentKitResult = await runJayStackAgentKit(projectRoot, pluginName);
2891
+ if (agentKitResult.code !== 0) {
2892
+ const msg = (agentKitResult.stderr || agentKitResult.stdout).trim();
2893
+ yield {
2894
+ type: "error",
2895
+ message: `agent-kit failed for ${pluginName}: ${msg || "unknown error"}`
2896
+ };
2897
+ return;
2898
+ }
2899
+ const contracts = await contractsForPlugin$1(projectRoot, pluginName);
2900
+ yield {
2901
+ type: "complete",
2902
+ pluginName,
2903
+ setupStatus: parsed.setupStatus,
2904
+ configFiles,
2905
+ contracts,
2906
+ message: parsed.message
2907
+ };
2908
+ }
2909
+ const PLUGIN_MANIFEST_START = "<!-- aiditor:plugins:start -->";
2910
+ const PLUGIN_MANIFEST_END = "<!-- aiditor:plugins:end -->";
2911
+ function formatManifestLine(packageName, contractName, componentKey) {
2912
+ const key = componentKey ?? getDefaultComponentKey(contractName);
2913
+ return `- ${packageName} / ${contractName} (key: ${key})`;
2914
+ }
2915
+ function buildPluginManifestSection(selectedPlugins) {
2916
+ const lines = [
2917
+ PLUGIN_MANIFEST_START,
2918
+ "",
2919
+ "## Plugins & contracts (selected)",
2920
+ ""
2921
+ ];
2922
+ for (const plugin of selectedPlugins) {
2923
+ for (const contract of plugin.contracts) {
2924
+ lines.push(
2925
+ formatManifestLine(
2926
+ plugin.packageName,
2927
+ contract.contractName,
2928
+ contract.componentKey
2929
+ )
2930
+ );
2931
+ }
2932
+ }
2933
+ lines.push(PLUGIN_MANIFEST_END);
2934
+ return lines.join("\n");
2935
+ }
2936
+ function syncPluginManifestInContentMd(contentMd, selectedPlugins) {
2937
+ const section = buildPluginManifestSection(selectedPlugins);
2938
+ const startIx = contentMd.indexOf(PLUGIN_MANIFEST_START);
2939
+ const endIx = contentMd.indexOf(PLUGIN_MANIFEST_END);
2940
+ if (startIx >= 0 && endIx > startIx) {
2941
+ const before = contentMd.slice(0, startIx).trimEnd();
2942
+ const after = contentMd.slice(endIx + PLUGIN_MANIFEST_END.length).trimStart();
2943
+ const parts = [before, section, after].filter((p) => p.length > 0);
2944
+ return parts.length > 0 ? `${parts.join("\n\n")}
2945
+ ` : `${section}
2946
+ `;
2947
+ }
2948
+ const hasContracts = selectedPlugins.some((p) => p.contracts.length > 0);
2949
+ if (!hasContracts) return contentMd;
2950
+ const trimmed = contentMd.trimEnd();
2951
+ if (trimmed.length === 0) {
2952
+ return `${section}
2953
+ `;
2954
+ }
2955
+ return `${trimmed}
2956
+
2957
+ ${section}
2958
+ `;
2959
+ }
2960
+ async function contractsForPlugin(projectRoot, pluginName) {
2961
+ const fromIndex = await parsePluginsIndexContracts(projectRoot);
2962
+ const filtered = fromIndex.filter((c) => c.pluginName === pluginName);
2963
+ if (filtered.length > 0) {
2964
+ return filtered.map((c) => ({
2965
+ contractName: c.contractName,
2966
+ description: c.description,
2967
+ isDynamic: c.isDynamic
2968
+ }));
2969
+ }
2970
+ const fromYaml = await parsePluginYamlContracts(projectRoot, pluginName);
2971
+ return fromYaml.map((c) => ({
2972
+ contractName: c.contractName,
2973
+ description: c.description
2974
+ }));
2975
+ }
2976
+ const listJayPluginsAction = makeJayQuery("aiditor.listJayPlugins").withServices(DEV_SERVER_SERVICE).withHandler(async (input, _devServer) => {
2977
+ const projectRoot = process.cwd();
2978
+ const catalog = mergeCatalogWithSources(
2979
+ getJayPluginCatalog(),
2980
+ (await readPluginSources(projectRoot)).sources
2981
+ );
2982
+ const installed = await getInstalledPlugins(projectRoot);
2983
+ const installedByName = new Map(installed.map((p) => [p.pluginName, p]));
2984
+ let cache2 = await readSetupCache(projectRoot);
2985
+ if (input.refreshSetupStatus) {
2986
+ for (const inst of installed) {
2987
+ const entry = getCatalogEntry(inst.pluginName);
2988
+ if (!entry?.configFiles?.length) continue;
2989
+ const setupResult = await runJayStackSetup(
2990
+ projectRoot,
2991
+ inst.pluginName
2992
+ );
2993
+ const parsed = parseSetupOutput(
2994
+ setupResult.stdout,
2995
+ setupResult.stderr,
2996
+ setupResult.code
2997
+ );
2998
+ await writeSetupCacheEntry(projectRoot, inst.pluginName, {
2999
+ setupStatus: parsed.setupStatus,
3000
+ configFiles: entry.configFiles ?? [],
3001
+ message: parsed.message,
3002
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
3003
+ });
3004
+ }
3005
+ cache2 = await readSetupCache(projectRoot);
3006
+ }
3007
+ const plugins = await Promise.all(
3008
+ catalog.map(async (entry) => {
3009
+ const inst = installedByName.get(entry.pluginName);
3010
+ const cached = getCachedSetupStatus(cache2, entry.pluginName);
3011
+ const contracts = entry.kind === "headless" && inst ? await contractsForPlugin(projectRoot, entry.pluginName) : [];
3012
+ const installedVersion = inst && isRegistryInstallSpec(inst.installSpec) ? await readInstalledPackageVersion(projectRoot, entry.packageName) : null;
3013
+ return {
3014
+ pluginName: entry.pluginName,
3015
+ packageName: entry.packageName,
3016
+ description: entry.description,
3017
+ kind: entry.kind,
3018
+ requires: entry.requires,
3019
+ installed: !!inst,
3020
+ installSpec: inst?.installSpec,
3021
+ installedVersion: installedVersion ?? void 0,
3022
+ isLocalSource: entry.isLocalSource,
3023
+ showInAddPagePicker: entry.showInAddPagePicker,
3024
+ setupStatus: cached?.setupStatus ?? "unknown",
3025
+ configFiles: cached?.configFiles ?? entry.configFiles ?? [],
3026
+ setupMessage: cached?.message,
3027
+ contracts
3028
+ };
3029
+ })
3030
+ );
3031
+ return { plugins };
3032
+ });
3033
+ const listPluginContractsAction = makeJayQuery(
3034
+ "aiditor.listPluginContracts"
3035
+ ).withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
3036
+ const projectRoot = process.cwd();
3037
+ const installed = await getInstalledPlugins(projectRoot);
3038
+ const isInstalled = installed.some(
3039
+ (p) => p.pluginName === input.pluginName
3040
+ );
3041
+ const contracts = isInstalled ? await contractsForPlugin(projectRoot, input.pluginName) : [];
3042
+ const entry = getCatalogEntry(input.pluginName);
3043
+ let setupRequiredMessage;
3044
+ if (isInstalled && contracts.length === 0 && entry?.kind === "headless") {
3045
+ setupRequiredMessage = "Run setup to load contracts (dynamic contracts may require Wix configuration).";
3046
+ }
3047
+ return {
3048
+ pluginName: input.pluginName,
3049
+ installed: isInstalled,
3050
+ contracts,
3051
+ setupRequiredMessage
3052
+ };
3053
+ });
3054
+ const checkPluginUpdateAction = makeJayQuery("aiditor.checkPluginUpdate").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
3055
+ const projectRoot = process.cwd();
3056
+ const entry = getCatalogEntry(input.pluginName);
3057
+ if (!entry) {
3058
+ return {
3059
+ pluginName: input.pluginName,
3060
+ packageName: "",
3061
+ installedVersion: null,
3062
+ latestVersion: null,
3063
+ updateAvailable: false,
3064
+ isLocalSource: false,
3065
+ message: `Unknown plugin: ${input.pluginName}`
3066
+ };
3067
+ }
3068
+ const installed = await getInstalledPlugins(projectRoot);
3069
+ const inst = installed.find((p) => p.pluginName === input.pluginName);
3070
+ if (!inst) {
3071
+ return {
3072
+ pluginName: input.pluginName,
3073
+ packageName: entry.packageName,
3074
+ installedVersion: null,
3075
+ latestVersion: null,
3076
+ updateAvailable: false,
3077
+ isLocalSource: false,
3078
+ message: "Plugin is not installed."
3079
+ };
3080
+ }
3081
+ return checkPluginUpdate(
3082
+ projectRoot,
3083
+ input.pluginName,
3084
+ entry.packageName,
3085
+ inst.installSpec
3086
+ );
3087
+ });
3088
+ const ensureProjectPluginAction = makeJayStream(
3089
+ "aiditor.ensureProjectPlugin"
3090
+ ).withServices(DEV_SERVER_SERVICE).withHandler(async function* (input, devServer) {
3091
+ const projectRoot = process.cwd();
3092
+ try {
3093
+ for await (const chunk of ensureProjectPlugin(
3094
+ projectRoot,
3095
+ input.pluginName,
3096
+ { installSpec: input.installSpec, upgrade: input.upgrade }
3097
+ )) {
3098
+ if (chunk.type === "progress") {
3099
+ yield chunk;
3100
+ } else if (chunk.type === "complete") {
3101
+ await devServer.refreshRoutes();
3102
+ yield {
3103
+ type: "complete",
3104
+ pluginName: chunk.pluginName,
3105
+ setupStatus: chunk.setupStatus,
3106
+ configFiles: chunk.configFiles,
3107
+ contracts: chunk.contracts.map((c) => ({
3108
+ contractName: c.contractName,
3109
+ description: c.description,
3110
+ isDynamic: c.isDynamic
3111
+ }))
3112
+ };
3113
+ } else if (chunk.type === "error") {
3114
+ yield {
3115
+ type: "error",
3116
+ message: chunk.message,
3117
+ installError: chunk.installError
3118
+ };
3119
+ }
3120
+ }
3121
+ } catch (err) {
3122
+ yield {
3123
+ type: "error",
3124
+ message: err instanceof Error ? err.message : String(err)
3125
+ };
3126
+ }
3127
+ yield { type: "done" };
3128
+ });
3129
+ const getPluginSetupStatusAction = makeJayQuery(
3130
+ "aiditor.getPluginSetupStatus"
3131
+ ).withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
3132
+ const projectRoot = process.cwd();
3133
+ const installed = await getInstalledPlugins(projectRoot);
3134
+ const targets = input.pluginName ? installed.filter((p) => p.pluginName === input.pluginName) : installed;
3135
+ const plugins = [];
3136
+ for (const inst of targets) {
3137
+ const entry = getCatalogEntry(inst.pluginName);
3138
+ if (input.refresh) {
3139
+ const setupResult = await runJayStackSetup(
3140
+ projectRoot,
3141
+ inst.pluginName
3142
+ );
3143
+ const parsed = parseSetupOutput(
3144
+ setupResult.stdout,
3145
+ setupResult.stderr,
3146
+ setupResult.code
3147
+ );
3148
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
3149
+ await writeSetupCacheEntry(projectRoot, inst.pluginName, {
3150
+ setupStatus: parsed.setupStatus,
3151
+ configFiles: entry?.configFiles ?? [],
3152
+ message: parsed.message,
3153
+ checkedAt
3154
+ });
3155
+ plugins.push({
3156
+ pluginName: inst.pluginName,
3157
+ setupStatus: parsed.setupStatus,
3158
+ configFiles: entry?.configFiles ?? [],
3159
+ message: parsed.message,
3160
+ checkedAt
3161
+ });
3162
+ } else {
3163
+ const cache2 = await readSetupCache(projectRoot);
3164
+ const cached = getCachedSetupStatus(cache2, inst.pluginName);
3165
+ plugins.push({
3166
+ pluginName: inst.pluginName,
3167
+ setupStatus: cached?.setupStatus ?? "unknown",
3168
+ configFiles: cached?.configFiles ?? entry?.configFiles ?? [],
3169
+ message: cached?.message,
3170
+ checkedAt: cached?.checkedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
3171
+ });
3172
+ }
3173
+ }
3174
+ return { plugins };
3175
+ });
3176
+ const rerunPluginSetupAction = makeJayQuery("aiditor.rerunPluginSetup").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
3177
+ const projectRoot = process.cwd();
3178
+ const entry = getCatalogEntry(input.pluginName);
3179
+ const setupResult = await runJayStackSetup(
3180
+ projectRoot,
3181
+ input.pluginName,
3182
+ input.force
3183
+ );
3184
+ const parsed = parseSetupOutput(
3185
+ setupResult.stdout,
3186
+ setupResult.stderr,
3187
+ setupResult.code
3188
+ );
3189
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
3190
+ await writeSetupCacheEntry(projectRoot, input.pluginName, {
3191
+ setupStatus: parsed.setupStatus,
3192
+ configFiles: entry?.configFiles ?? parsed.configCreated ?? [],
3193
+ message: parsed.message,
3194
+ checkedAt
3195
+ });
3196
+ return {
3197
+ pluginName: input.pluginName,
3198
+ setupStatus: parsed.setupStatus,
3199
+ configCreated: parsed.configCreated,
3200
+ message: parsed.message
3201
+ };
3202
+ });
3203
+ const syncAddPagePluginManifestAction = makeJayQuery(
3204
+ "aiditor.syncAddPagePluginManifest"
3205
+ ).withServices(DEV_SERVER_SERVICE).withHandler(
3206
+ async (input) => {
3207
+ const projectRoot = process.cwd();
3208
+ const request = await readPageRequest(projectRoot, input.requestId);
3209
+ if (!request) {
3210
+ return { ok: false, contentMd: "", error: "Page request not found." };
3211
+ }
3212
+ const requestDir = path.join(
3213
+ projectRoot,
3214
+ ".aiditor",
3215
+ "page-requests",
3216
+ input.requestId
3217
+ );
3218
+ const contentPath = path.join(requestDir, "content.md");
3219
+ let contentMd = "";
3220
+ try {
3221
+ contentMd = await fs.readFile(contentPath, "utf-8");
3222
+ } catch {
3223
+ contentMd = "";
3224
+ }
3225
+ const nextMd = syncPluginManifestInContentMd(
3226
+ contentMd,
3227
+ input.selectedPlugins
3228
+ );
3229
+ await writePageRequestMarkdown(projectRoot, input.requestId, {
3230
+ contentMd: nextMd,
3231
+ designMd: await fs.readFile(path.join(requestDir, "design.md"), "utf-8").catch(() => "")
3232
+ });
3233
+ await writePageRequest(projectRoot, {
3234
+ ...request,
3235
+ selectedPlugins: input.selectedPlugins,
3236
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3237
+ });
3238
+ return { ok: true, contentMd: nextMd };
3239
+ }
3240
+ );
3241
+ const writePluginSourceAction = makeJayQuery("aiditor.writePluginSource").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
3242
+ const projectRoot = process.cwd();
3243
+ await writePluginSource(projectRoot, input);
3244
+ return { ok: true };
3245
+ });
595
3246
  setActionCallerOptions({ timeout: 12e4 });
596
3247
  const page = makeJayStackComponent().withProps();
597
3248
  const aiditorShell = makeJayStackComponent().withProps().withSlowlyRender(async () => {
@@ -608,10 +3259,27 @@ const aiditorShell = makeJayStackComponent().withProps().withSlowlyRender(async
608
3259
  export {
609
3260
  page as aiditorPage,
610
3261
  aiditorShell,
3262
+ checkAddPageRouteAction,
3263
+ checkPageAddPageBriefAction,
3264
+ checkPluginUpdateAction,
3265
+ ensureProjectPluginAction,
3266
+ generateAddPageBriefFromImageAction,
611
3267
  getAiditorBootstrap,
612
3268
  getPageParamsAction,
3269
+ getPluginSetupStatusAction,
613
3270
  getProjectInfoAction,
614
3271
  listFreezesAction,
3272
+ listJayPluginsAction,
3273
+ listPluginContractsAction,
3274
+ openAddPageFromBriefAction,
615
3275
  readFileAction,
616
- submitTaskAction
3276
+ reclassifyAddPageAssetAction,
3277
+ rerunPluginSetupAction,
3278
+ saveAddPageDraftAction,
3279
+ startAddPageRequestAction,
3280
+ submitAddPageAction,
3281
+ submitTaskAction,
3282
+ syncAddPagePluginManifestAction,
3283
+ uploadAddPageAssetAction,
3284
+ writePluginSourceAction
617
3285
  };