@easysociable/cli 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +32 -21
  2. package/{skills/easysociable-content → content-engine}/scripts/starter.mjs +3 -1
  3. package/{skills/easysociable-content → content-engine}/scripts/workbench.mjs +13 -8
  4. package/{skills/easysociable-content → content-engine}/starter/CLAUDE.md +217 -128
  5. package/{skills/easysociable-content → content-engine}/starter/engine.json +3 -0
  6. package/content-engine/starter/needs/README.md +38 -0
  7. package/content-engine/starter/needs/_index.md +31 -0
  8. package/content-engine/starter/needs/_template.md +24 -0
  9. package/content-engine/starter/needs/entries/README.md +5 -0
  10. package/content-engine/starter/products/README.md +38 -0
  11. package/content-engine/starter/products/_index.md +24 -0
  12. package/content-engine/starter/products/_template.md +56 -0
  13. package/content-engine/starter/products/entries/P-001-easysociable.md +150 -0
  14. package/content-engine/starter/profiles/_index.md +16 -0
  15. package/{skills/easysociable-content → content-engine}/starter/profiles/default/README.md +4 -1
  16. package/content-engine/starter/profiles/default/boundaries.md +14 -0
  17. package/content-engine/starter/profiles/default/expression.md +18 -0
  18. package/content-engine/starter/profiles/default/lanes.md +12 -0
  19. package/content-engine/starter/profiles/default/stories.md +15 -0
  20. package/{skills/easysociable-content → content-engine}/starter/profiles/default/voice.md +14 -0
  21. package/content-engine/starter/recommendations/README.md +21 -0
  22. package/content-engine/starter/recommendations/_index.md +21 -0
  23. package/content-engine/starter/recommendations/_template.md +44 -0
  24. package/{skills/easysociable-content → content-engine}/starter/runs/_template/brief.md +1 -1
  25. package/{skills/easysociable-content → content-engine}/starter/runs/_template/pack.md +15 -7
  26. package/{skills/easysociable-content → content-engine}/starter/runs/_template/packet.md +6 -0
  27. package/{skills/easysociable-content → content-engine}/starter/swipe/_template.md +3 -1
  28. package/content-engine/starter/topics/README.md +21 -0
  29. package/{skills/easysociable-content → content-engine}/starter/topics/_index.md +12 -5
  30. package/content-engine/starter/topics/_template-item.md +103 -0
  31. package/{skills/easysociable-content → content-engine}/starter/topics/scoring.md +55 -4
  32. package/content-engine/workbench/__pycache__/build.cpython-310.pyc +0 -0
  33. package/{skills/easysociable-content → content-engine}/workbench/build.py +306 -97
  34. package/{skills/easysociable-content → content-engine}/workbench/serve.py +0 -0
  35. package/{skills/easysociable-content → content-engine}/workbench/template.html +173 -27
  36. package/dist/easysociable.js +629 -142
  37. package/dist/easysociable.js.map +1 -1
  38. package/package.json +15 -15
  39. package/skills/ONBOARDING.md +21 -18
  40. package/skills/easysociable-content/SKILL.md +104 -98
  41. package/skills/easysociable-content/workflows/INTERVIEW.md +70 -0
  42. package/skills/easysociable-content/workflows/NEEDS.md +82 -0
  43. package/skills/easysociable-slideshow/SKILL.md +79 -44
  44. package/skills/easysociable-slideshow/references/ARC.md +44 -0
  45. package/skills/easysociable-slideshow/references/CONTENT_PACK.md +34 -6
  46. package/skills/easysociable-slideshow/workflows/CREATE_RUN.md +24 -5
  47. package/skills/easysociable-slideshow/workflows/OUT_OF_SCOPE.md +3 -0
  48. package/skills/easysociable-template/SKILL.md +29 -30
  49. package/skills/easysociable-template/references/CATALOG_METADATA.md +23 -20
  50. package/skills/easysociable-template/references/DESIGN_V2_1.md +53 -2
  51. package/skills/easysociable-template/references/GATEWAY.md +7 -2
  52. package/skills/easysociable-template/references/PLATFORM_SPECS.md +11 -3
  53. package/skills/easysociable-template/references/THEME_TOKENS.md +1 -1
  54. package/skills/easysociable-template/references/fixtures/image-reference.design-plan.json +74 -0
  55. package/skills/easysociable-template/references/fixtures/image-reference.visual-qa.json +28 -0
  56. package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
  57. package/skills/easysociable-template/scripts/validate-design-v2.mjs +97 -0
  58. package/skills/easysociable-template/workflows/ASSET_PLAN.md +2 -1
  59. package/skills/easysociable-template/workflows/AUTHORING_FROM_BRIEF.md +4 -4
  60. package/skills/easysociable-template/workflows/AUTHORING_FROM_IMAGE.md +66 -16
  61. package/skills/easysociable-template/workflows/AUTHORING_STUDIO.md +28 -15
  62. package/skills/easysociable-template/workflows/DESIGN_BRIEF.md +4 -3
  63. package/skills/easysociable-template/workflows/FILL_AND_RENDER.md +4 -83
  64. package/skills/easysociable-template/workflows/MULTI_PLATFORM_FAMILY.md +81 -0
  65. package/skills/easysociable-template/workflows/OUT_OF_SCOPE.md +3 -2
  66. package/skills/easysociable-template/workflows/PUBLISH_FROM_DRAFT.md +1 -1
  67. package/skills/easysociable-template/workflows/VISUAL_QA.md +10 -0
  68. package/skills/easysociable-content/INSTALL.md +0 -60
  69. package/skills/easysociable-content/platforms/claude.md +0 -4
  70. package/skills/easysociable-content/platforms/codex.md +0 -4
  71. package/skills/easysociable-content/platforms/hermes.md +0 -4
  72. package/skills/easysociable-content/platforms/openclaw.md +0 -4
  73. package/skills/easysociable-content/references/CLI.md +0 -40
  74. package/skills/easysociable-content/references/CONTRACT_SYNC.md +0 -42
  75. package/skills/easysociable-content/references/MCP.md +0 -15
  76. package/skills/easysociable-content/references/PAGE_PACK.md +0 -129
  77. package/skills/easysociable-content/references/PLATFORM_GUIDE.md +0 -36
  78. package/skills/easysociable-content/references/RUN_LAYOUT.md +0 -73
  79. package/skills/easysociable-content/references/TROUBLESHOOTING.md +0 -13
  80. package/skills/easysociable-content/references/VAULT.md +0 -40
  81. package/skills/easysociable-content/references/VOICE.md +0 -16
  82. package/skills/easysociable-content/scripts/package-skill.mjs +0 -49
  83. package/skills/easysociable-content/scripts/setup.mjs +0 -200
  84. package/skills/easysociable-content/scripts/setup.test.mjs +0 -82
  85. package/skills/easysociable-content/starter/discovery/README.md +0 -18
  86. package/skills/easysociable-content/starter/discovery/_index.md +0 -27
  87. package/skills/easysociable-content/starter/discovery/_template.md +0 -41
  88. package/skills/easysociable-content/starter/profiles/_index.md +0 -13
  89. package/skills/easysociable-content/starter/topics/README.md +0 -25
  90. package/skills/easysociable-content/starter/topics/WATCHLIST.md +0 -65
  91. package/skills/easysociable-content/starter/topics/_template-item.md +0 -90
  92. package/skills/easysociable-content/starter/topics/watchlists/README.md +0 -10
  93. package/skills/easysociable-content/starter/topics/watchlists/_shared.md +0 -14
  94. package/skills/easysociable-content/starter/topics/watchlists/default.md +0 -6
  95. package/skills/easysociable-content/workflows/ANALYSIS_AND_COVERAGE.md +0 -17
  96. package/skills/easysociable-content/workflows/CONTENT_EXPORT.md +0 -24
  97. package/skills/easysociable-content/workflows/GETTING_STARTED.md +0 -40
  98. package/skills/easysociable-content/workflows/MCP_FIRST.md +0 -5
  99. package/skills/easysociable-content/workflows/OUT_OF_SCOPE.md +0 -18
  100. package/skills/easysociable-content/workflows/PACK_AUTHORING.md +0 -42
  101. package/skills/easysociable-content/workflows/SWIPE_CAPTURE.md +0 -18
  102. package/skills/easysociable-content/workflows/TOPIC_DRAFT.md +0 -29
  103. package/skills/easysociable-content/workflows/WORKING_DRAFT.md +0 -19
  104. /package/{skills/easysociable-content → content-engine}/fixtures/golden-run-en/draft.md +0 -0
  105. /package/{skills/easysociable-content → content-engine}/fixtures/golden-run-en/idea.md +0 -0
  106. /package/{skills/easysociable-content → content-engine}/fixtures/golden-run-en/pack-tiktok.json +0 -0
  107. /package/{skills/easysociable-content → content-engine}/scripts/contracts.mjs +0 -0
  108. /package/{skills/easysociable-content → content-engine}/scripts/export-contracts.mjs +0 -0
  109. /package/{skills/easysociable-content → content-engine}/scripts/fixture.test.mjs +0 -0
  110. /package/{skills/easysociable-content → content-engine}/scripts/validate-export.mjs +0 -0
  111. /package/{skills/easysociable-content → content-engine}/scripts/validate-pack.mjs +0 -0
  112. /package/{skills/easysociable-content → content-engine}/scripts/validate-run.mjs +0 -0
  113. /package/{skills/easysociable-content → content-engine}/scripts/validate.test.mjs +0 -0
  114. /package/{skills/easysociable-content → content-engine}/starter/accounts/README.md +0 -0
  115. /package/{skills/easysociable-content → content-engine}/starter/accounts/_index.md +0 -0
  116. /package/{skills/easysociable-content → content-engine}/starter/accounts/overrides/README.md +0 -0
  117. /package/{skills/easysociable-content → content-engine}/starter/exports/README.md +0 -0
  118. /package/{skills/easysociable-content → content-engine}/starter/inbox/README.md +0 -0
  119. /package/{skills/easysociable-content → content-engine}/starter/inbox/_index.md +0 -0
  120. /package/{skills/easysociable-content → content-engine}/starter/inbox/entries/README.md +0 -0
  121. /package/{skills/easysociable-content → content-engine}/starter/platforms/README.md +0 -0
  122. /package/{skills/easysociable-content → content-engine}/starter/platforms/ig.md +0 -0
  123. /package/{skills/easysociable-content → content-engine}/starter/platforms/instagram.md +0 -0
  124. /package/{skills/easysociable-content → content-engine}/starter/platforms/linkedin.md +0 -0
  125. /package/{skills/easysociable-content → content-engine}/starter/platforms/threads.md +0 -0
  126. /package/{skills/easysociable-content → content-engine}/starter/platforms/tiktok.md +0 -0
  127. /package/{skills/easysociable-content → content-engine}/starter/platforms/x.md +0 -0
  128. /package/{skills/easysociable-content → content-engine}/starter/platforms/xiaohongshu.md +0 -0
  129. /package/{skills/easysociable-content → content-engine}/starter/profiles/default/audience.md +0 -0
  130. /package/{skills/easysociable-content → content-engine}/starter/profiles/default/pillars.md +0 -0
  131. /package/{skills/easysociable-content → content-engine}/starter/published/_index.md +0 -0
  132. /package/{skills/easysociable-content → content-engine}/starter/raw/README.md +0 -0
  133. /package/{skills/easysociable-content → content-engine}/starter/raw/_index.md +0 -0
  134. /package/{skills/easysociable-content → content-engine}/starter/runs/README.md +0 -0
  135. /package/{skills/easysociable-content → content-engine}/starter/runs/_index.md +0 -0
  136. /package/{skills/easysociable-content → content-engine}/starter/runs/_template/draft.md +0 -0
  137. /package/{skills/easysociable-content → content-engine}/starter/runs/_template/editor.md +0 -0
  138. /package/{skills/easysociable-content → content-engine}/starter/runs/_template/feedback.md +0 -0
  139. /package/{skills/easysociable-content → content-engine}/starter/runs/_template/idea.md +0 -0
  140. /package/{skills/easysociable-content → content-engine}/starter/runs/_template/rubric.md +0 -0
  141. /package/{skills/easysociable-content → content-engine}/starter/swipe/_index.md +0 -0
  142. /package/{skills/easysociable-content → content-engine}/starter/topics/items/README.md +0 -0
  143. /package/{skills/easysociable-content → content-engine}/starter/wiki/README.md +0 -0
  144. /package/{skills/easysociable-content → content-engine}/starter/wiki/atoms/_index.md +0 -0
  145. /package/{skills/easysociable-content → content-engine}/starter/wiki/atoms/_template.md +0 -0
  146. /package/{skills/easysociable-content → content-engine}/starter/wiki/claims/_index.md +0 -0
  147. /package/{skills/easysociable-content → content-engine}/starter/wiki/claims/_template.md +0 -0
  148. /package/{skills/easysociable-content → content-engine}/starter/workbench/README.md +0 -0
@@ -15351,13 +15351,17 @@ function parseSlideshowRunSummaryList(payload) {
15351
15351
  assertNoPrivateTemplateFields(payload);
15352
15352
  return parseDataEnvelope(payload, external_exports.array(slideshowRunSummarySchema));
15353
15353
  }
15354
+ function parseSlideshowRunSummary(payload) {
15355
+ assertNoPrivateTemplateFields(payload);
15356
+ return parseDataEnvelope(payload, slideshowRunSummarySchema);
15357
+ }
15354
15358
  function parseBrand(payload) {
15355
15359
  return parseDataEnvelope(payload, brandSchema);
15356
15360
  }
15357
15361
  function parseBrandList(payload) {
15358
15362
  return parseDataEnvelope(payload, external_exports.array(brandSchema));
15359
15363
  }
15360
- var FORBIDDEN_PUBLIC_KEYS, templatePlatformSchema, canvasPresetSchema, pageRoleSchema, contentFamilySchema, contentArchetypeSchema, densityLevelSchema, visualStyleSchema, imageRoleSchema, slotSummarySchema, selectionMetadataSchema, selectionMetadataDraftSchema, listTemplatesQuerySchema, templateSummarySchema, publicTemplateSlotTypeSchema, publicTemplateSlotSchema, templateDetailSchema, templateSelectionSchema, presentationIntentSchema, renderSlotValueSchema, renderJobRequestSchema, legacyRenderJobRequestSchema, renderJobStatusSchema, renderCaptureHintSchema, renderJobViewSchema, renderPreviewMaterializationSchema, renderSelectionSummarySchema, renderManifestSchema, templateAuthoringCanvasSchema, templateAuthoringProvenanceSchema, templateImageModeSchema, templateImageNodeSchema, authoringImageNodeSchema, authoringIconNodeSchema, templateAuthoringSourceSchema, themeContrastEntrySchema, themePaletteSchema, themeTokensSchema, themeSummarySchema, themeDetailSchema, gradientTextSafeZoneSchema, gradientStopSchema, gradientSummarySchema, gradientDetailSchema, listGradientsQuerySchema, templateSurfaceKindSchema, templateSurfacePreviewSchema, templateImageProviderSchema, templateImageRightsStatusSchema, templateImageProvenanceSchema, importTemplateImageFileSchema, importTemplateImageInputSchema, importTemplateImageBodySchema, createRenderAssetInputSchema, renderAssetReferenceSchema, iconStyleSchema, templateIconLicenseSchema, searchTemplateIconsRequestSchema, templateIconCandidateSchema, getTemplateIconRequestSchema, templateIconProvenanceSchema, templateIconAssetReferenceSchema, templateIconErrorCodeSchema, templateImageReferenceSchema, themeCreateInputSchema, draftThemeInputSchema, createTemplateDraftInputSchema, updateTemplateDraftInputSchema, templateAuthoringDraftSchema, DEFAULT_AUTHORING_STUDIO_BASE_URL, templatePrincipalErrorSchema, slideshowPlatformCaps, slideshowPlatformSchema, slideshowRunStatusSchema, publicSlotValueSchema, contentPackPageSchema, contentPackSchema, slideshowRunPageSchema, brandProfilePreferenceSchema, brandProfileSchema, createBrandProfileInputSchema, updateBrandProfileInputSchema, brandTypographySchema, brandColorsSchema, brandSchema, createBrandInputSchema, updateBrandInputSchema, slideshowRunSummarySchema, slideshowRunSchema, createSlideshowRunInputSchema, updateSlideshowRunInputSchema, renderSlideshowRunInputSchema, listSlideshowRunsQuerySchema;
15364
+ var FORBIDDEN_PUBLIC_KEYS, templatePlatformSchema, canvasPresetSchema, pageRoleSchema, contentFamilySchema, contentArchetypeSchema, densityLevelSchema, visualStyleSchema, imageRoleSchema, slotSummarySchema, selectionMetadataSchema, selectionMetadataDraftSchema, listTemplatesQuerySchema, templateSummarySchema, publicTemplateSlotTypeSchema, publicTemplateSlotSchema, templateDetailSchema, templateSelectionSchema, presentationIntentSchema, renderSlotValueSchema, renderJobRequestSchema, legacyRenderJobRequestSchema, renderJobStatusSchema, renderCaptureHintSchema, renderJobViewSchema, renderPreviewMaterializationSchema, renderSelectionSummarySchema, renderManifestSchema, templateAuthoringCanvasSchema, templateAuthoringProvenanceSchema, templateImageModeSchema, templateImageNodeSchema, authoringImageNodeSchema, authoringIconNodeSchema, templateAuthoringSourceSchema, themeContrastEntrySchema, themePaletteSchema, themeTokensSchema, themeSummarySchema, themeDetailSchema, gradientTextSafeZoneSchema, gradientStopSchema, gradientSummarySchema, gradientDetailSchema, listGradientsQuerySchema, templateSurfaceKindSchema, templateSurfacePreviewSchema, templateImageProviderSchema, templateImageRightsStatusSchema, templateImageProvenanceSchema, importTemplateImageFileSchema, importTemplateImageInputSchema, importTemplateImageBodySchema, createRenderAssetInputSchema, renderAssetReferenceSchema, iconStyleSchema, templateIconLicenseSchema, searchTemplateIconsRequestSchema, templateIconCandidateSchema, getTemplateIconRequestSchema, templateIconProvenanceSchema, templateIconAssetReferenceSchema, templateIconErrorCodeSchema, templateImageReferenceSchema, themeCreateInputSchema, draftThemeInputSchema, createTemplateDraftInputSchema, updateTemplateDraftInputSchema, authoringDraftVariantSchema, templateAuthoringDraftSchema, DEFAULT_AUTHORING_STUDIO_BASE_URL, templatePrincipalErrorSchema, slideshowPlatformCaps, slideshowPlatformSchema, slideshowRunStatusSchema, publicSlotValueSchema, contentPackPageSchema, contentPackSchema, slideshowRunPageSchema, brandProfilePreferenceSchema, brandProfileSchema, createBrandProfileInputSchema, updateBrandProfileInputSchema, brandTypographySchema, brandColorsSchema, brandSchema, createBrandInputSchema, updateBrandInputSchema, slideshowRunSummarySchema, slideshowRunSchema, createSlideshowRunInputSchema, updateSlideshowRunInputSchema, renderSlideshowRunInputSchema, listSlideshowRunsQuerySchema;
15361
15365
  var init_src = __esm({
15362
15366
  "../../packages/template-public-contracts/src/index.ts"() {
15363
15367
  "use strict";
@@ -15487,8 +15491,8 @@ var init_src = __esm({
15487
15491
  notFor: external_exports.array(external_exports.string()).min(1).max(3).optional(),
15488
15492
  selectionHints: external_exports.string().optional(),
15489
15493
  visualStyle: visualStyleSchema.optional(),
15490
- defaultThemeId: external_exports.string().optional(),
15491
- compatibleThemeIds: external_exports.array(external_exports.string()).optional(),
15494
+ // ADR-0033: published selection metadata must not bind a Theme.
15495
+ // Runtime appearance comes from Brand themeSnapshot at fill/render.
15492
15496
  locale: external_exports.enum(["zh-CN", "en", "bilingual"]).optional(),
15493
15497
  exampleFilledPreviewUrl: external_exports.string().url().optional(),
15494
15498
  // P2
@@ -15538,7 +15542,10 @@ var init_src = __esm({
15538
15542
  notFor: external_exports.array(external_exports.string()).optional(),
15539
15543
  locale: external_exports.enum(["zh-CN", "en", "bilingual"]).optional(),
15540
15544
  capacity: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
15541
- previewImageUrl: external_exports.string().url().optional()
15545
+ previewImageUrl: external_exports.string().url().optional(),
15546
+ /** Layout Family id — catalog UIs group variants by this (ADR-0033). */
15547
+ familyId: external_exports.string().optional(),
15548
+ familyRole: external_exports.enum(["master", "variant"]).optional()
15542
15549
  }).strict();
15543
15550
  publicTemplateSlotTypeSchema = external_exports.enum(["text", "number", "image"]);
15544
15551
  publicTemplateSlotSchema = external_exports.object({
@@ -15630,7 +15637,11 @@ var init_src = __esm({
15630
15637
  }).strict();
15631
15638
  renderSelectionSummarySchema = external_exports.object({
15632
15639
  templateId: external_exports.string().min(1),
15633
- themeId: external_exports.string().min(1),
15640
+ /**
15641
+ * Optional audit id for the tokens used (Brand snapshot / preview fallback).
15642
+ * Not a Template→Theme publish binding (ADR-0033).
15643
+ */
15644
+ themeId: external_exports.string().min(1).optional(),
15634
15645
  role: external_exports.string().min(1),
15635
15646
  canvas: external_exports.object({
15636
15647
  width: external_exports.number().int().positive(),
@@ -15999,23 +16010,18 @@ var init_src = __esm({
15999
16010
  tokens: themeTokensSchema
16000
16011
  }).strict();
16001
16012
  draftThemeInputSchema = external_exports.object({
16013
+ /** @deprecated TemplateTheme retired — ignored; use create.tokens or tokens. */
16002
16014
  themeId: external_exports.string().min(1).optional(),
16003
- /** With themeId: fork a new Theme when tokens differ (never mutate shared themes). */
16015
+ /** Preferred: write these onto LayoutFamily.preview_tokens. */
16004
16016
  tokens: themeTokensSchema.optional(),
16005
16017
  create: themeCreateInputSchema.optional()
16006
16018
  }).strict().refine(
16007
16019
  (value) => {
16008
- const hasId = Boolean(value.themeId);
16009
16020
  const hasCreate = Boolean(value.create);
16010
- if (hasId && hasCreate)
16011
- return false;
16012
- if (!hasId && !hasCreate)
16013
- return false;
16014
- if (value.tokens && !hasId)
16015
- return false;
16016
- return true;
16021
+ const hasTokens = Boolean(value.tokens);
16022
+ return hasCreate || hasTokens;
16017
16023
  },
16018
- { message: "theme must provide themeId (optionally with tokens) or create" }
16024
+ { message: "theme must provide create.tokens or tokens for Family.preview_tokens" }
16019
16025
  );
16020
16026
  createTemplateDraftInputSchema = external_exports.object({
16021
16027
  name: external_exports.string().min(1),
@@ -16035,12 +16041,28 @@ var init_src = __esm({
16035
16041
  theme: draftThemeInputSchema.optional(),
16036
16042
  selectionMetadata: selectionMetadataDraftSchema.optional()
16037
16043
  }).strict();
16044
+ authoringDraftVariantSchema = external_exports.object({
16045
+ pageId: external_exports.string(),
16046
+ familyRole: external_exports.enum(["master", "variant"]),
16047
+ canvasPreset: external_exports.string().nullable().optional(),
16048
+ canvas: external_exports.object({
16049
+ width: external_exports.number(),
16050
+ height: external_exports.number()
16051
+ }),
16052
+ platforms: external_exports.array(external_exports.string())
16053
+ }).strict();
16038
16054
  templateAuthoringDraftSchema = external_exports.object({
16039
16055
  id: external_exports.string(),
16040
16056
  /** Equals draft id — drafts are folded into TemplatePage. */
16041
16057
  templateId: external_exports.string().optional(),
16042
16058
  /** Soft theme preview reference; tokens are not stored on the draft. */
16043
16059
  themeId: external_exports.string().optional(),
16060
+ /** Alias of themeId after Layout Family / Theme unbind. */
16061
+ previewThemeId: external_exports.string().nullable().optional(),
16062
+ familyId: external_exports.string().optional(),
16063
+ familyRole: external_exports.enum(["master", "variant"]).optional(),
16064
+ variants: external_exports.array(authoringDraftVariantSchema).optional(),
16065
+ intendedPlatforms: external_exports.array(external_exports.string()).optional(),
16044
16066
  name: external_exports.string(),
16045
16067
  status: external_exports.enum(["draft", "validated", "published", "archived", "trashed"]),
16046
16068
  revision: external_exports.number().int().positive().optional(),
@@ -16273,7 +16295,7 @@ function isRetryableStatus(status) {
16273
16295
  return status === 429 || status === 502 || status === 503 || status === 504;
16274
16296
  }
16275
16297
  function sleep(ms) {
16276
- return new Promise((resolve) => setTimeout(resolve, ms));
16298
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
16277
16299
  }
16278
16300
  var TemplateGatewayError, TemplateGatewayClient;
16279
16301
  var init_client = __esm({
@@ -16449,6 +16471,25 @@ var init_client = __esm({
16449
16471
  this.options.authoringStudioBaseUrl
16450
16472
  );
16451
16473
  }
16474
+ async getTemplateFamily(familyId) {
16475
+ const response = await this.request("GET", `/v1/template-families/${encodeURIComponent(familyId)}`);
16476
+ this.throwIfError(response.status, response.json);
16477
+ return response.json.data ?? response.json;
16478
+ }
16479
+ async deriveTemplateFamily(familyId, body = {}) {
16480
+ const response = await this.request(
16481
+ "POST",
16482
+ `/v1/template-families/${encodeURIComponent(familyId)}/derive`,
16483
+ body
16484
+ );
16485
+ this.throwIfError(response.status, response.json);
16486
+ return response.json.data ?? response.json;
16487
+ }
16488
+ async migrateLegacyTemplateFamilies() {
16489
+ const response = await this.request("POST", "/v1/template-families/migrate-legacy", {});
16490
+ this.throwIfError(response.status, response.json);
16491
+ return response.json.data;
16492
+ }
16452
16493
  async updateDraft(draftId, body) {
16453
16494
  const input = parseUpdateTemplateDraftInput(body);
16454
16495
  const response = await this.request("PUT", `/v1/template-drafts/${encodeURIComponent(draftId)}`, input);
@@ -16535,11 +16576,11 @@ var init_client = __esm({
16535
16576
  this.throwIfError(response.status, response.json);
16536
16577
  return parseSlideshowRun(response.json);
16537
16578
  }
16579
+ /** Permanently delete a Slideshow Run (ADR-0034 hard delete). */
16538
16580
  async trashSlideshowRun(runId) {
16539
16581
  const response = await this.request("DELETE", `/v1/slideshow/runs/${encodeURIComponent(runId)}`);
16540
16582
  this.throwIfError(response.status, response.json);
16541
- const rows = parseSlideshowRunSummaryList(response.json);
16542
- return rows[0];
16583
+ return parseSlideshowRunSummary(response.json);
16543
16584
  }
16544
16585
  async renderSlideshowRun(runId, body) {
16545
16586
  const input = renderSlideshowRunInputSchema.parse(body ?? {});
@@ -23418,7 +23459,7 @@ var init_protocol = __esm({
23418
23459
  return;
23419
23460
  }
23420
23461
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
23421
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
23462
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
23422
23463
  options?.signal?.throwIfAborted();
23423
23464
  }
23424
23465
  } catch (error52) {
@@ -23435,7 +23476,7 @@ var init_protocol = __esm({
23435
23476
  */
23436
23477
  request(request, resultSchema, options) {
23437
23478
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
23438
- return new Promise((resolve, reject) => {
23479
+ return new Promise((resolve3, reject) => {
23439
23480
  const earlyReject = (error52) => {
23440
23481
  reject(error52);
23441
23482
  };
@@ -23513,7 +23554,7 @@ var init_protocol = __esm({
23513
23554
  if (!parseResult.success) {
23514
23555
  reject(parseResult.error);
23515
23556
  } else {
23516
- resolve(parseResult.data);
23557
+ resolve3(parseResult.data);
23517
23558
  }
23518
23559
  } catch (error52) {
23519
23560
  reject(error52);
@@ -23774,12 +23815,12 @@ var init_protocol = __esm({
23774
23815
  }
23775
23816
  } catch {
23776
23817
  }
23777
- return new Promise((resolve, reject) => {
23818
+ return new Promise((resolve3, reject) => {
23778
23819
  if (signal.aborted) {
23779
23820
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
23780
23821
  return;
23781
23822
  }
23782
- const timeoutId = setTimeout(resolve, interval);
23823
+ const timeoutId = setTimeout(resolve3, interval);
23783
23824
  signal.addEventListener("abort", () => {
23784
23825
  clearTimeout(timeoutId);
23785
23826
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -26806,7 +26847,7 @@ var require_compile = __commonJS({
26806
26847
  const schOrFunc = root.refs[ref];
26807
26848
  if (schOrFunc)
26808
26849
  return schOrFunc;
26809
- let _sch = resolve.call(this, root, ref);
26850
+ let _sch = resolve3.call(this, root, ref);
26810
26851
  if (_sch === void 0) {
26811
26852
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
26812
26853
  const { schemaId } = this.opts;
@@ -26833,7 +26874,7 @@ var require_compile = __commonJS({
26833
26874
  function sameSchemaEnv(s1, s2) {
26834
26875
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
26835
26876
  }
26836
- function resolve(root, ref) {
26877
+ function resolve3(root, ref) {
26837
26878
  let sch;
26838
26879
  while (typeof (sch = this.refs[ref]) == "string")
26839
26880
  ref = sch;
@@ -27408,7 +27449,7 @@ var require_fast_uri = __commonJS({
27408
27449
  }
27409
27450
  return uri;
27410
27451
  }
27411
- function resolve(baseURI, relativeURI, options) {
27452
+ function resolve3(baseURI, relativeURI, options) {
27412
27453
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
27413
27454
  const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
27414
27455
  schemelessOptions.skipEscape = true;
@@ -27635,7 +27676,7 @@ var require_fast_uri = __commonJS({
27635
27676
  var fastUri = {
27636
27677
  SCHEMES,
27637
27678
  normalize,
27638
- resolve,
27679
+ resolve: resolve3,
27639
27680
  resolveComponent,
27640
27681
  equal,
27641
27682
  serialize,
@@ -31534,12 +31575,12 @@ var init_stdio2 = __esm({
31534
31575
  this.onclose?.();
31535
31576
  }
31536
31577
  send(message) {
31537
- return new Promise((resolve) => {
31578
+ return new Promise((resolve3) => {
31538
31579
  const json2 = serializeMessage(message);
31539
31580
  if (this._stdout.write(json2)) {
31540
- resolve();
31581
+ resolve3();
31541
31582
  } else {
31542
- this._stdout.once("drain", resolve);
31583
+ this._stdout.once("drain", resolve3);
31543
31584
  }
31544
31585
  });
31545
31586
  }
@@ -31610,8 +31651,8 @@ async function serveStdioMcp(options) {
31610
31651
  await waitForStdioServerClose(server, process.stdin);
31611
31652
  }
31612
31653
  async function waitForStdioServerClose(server, stdin = process.stdin) {
31613
- await new Promise((resolve) => {
31614
- server.onclose = () => resolve();
31654
+ await new Promise((resolve3) => {
31655
+ server.onclose = () => resolve3();
31615
31656
  if (stdin.readableEnded || stdin.destroyed) {
31616
31657
  void server.close().catch(() => void 0);
31617
31658
  return;
@@ -31940,7 +31981,7 @@ var init_src4 = __esm({
31940
31981
  {
31941
31982
  name: "create_render_job",
31942
31983
  title: "Create render job",
31943
- description: "Submit fill-slots hosted render: templateId + slots + outputFormats (ADR-0021). Image slot values must be renderAssetId (preferred) or catalog imageId. Do not send ContentDocument, contentDocumentHash, AST, Binding Map, or RenderPlan.",
31984
+ description: "Retired as a paid delivery path. PNG packs come from Studio Download. Gateway returns HEADLESS_RENDER_DISABLED. Kept for compatibility; do not use for customer PNG delivery. Historic fill-slots shape: templateId + slots + outputFormats (ADR-0021).",
31944
31985
  effect: "local-write-job",
31945
31986
  inputSchema: {
31946
31987
  type: "object",
@@ -32241,8 +32282,8 @@ var init_src5 = __esm({
32241
32282
  { name: "update_run", title: "Update slideshow run", description: "Edit page-instance slots/layout/style/decorations or presentation preference with optimistic revision.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { runId: { type: "string" }, pack: { type: "object" }, brandId: { type: "string" }, brandProfileId: { type: "string" }, contentSlug: { type: "string" }, baseRevision: { type: "integer", minimum: 1 }, pageIndex: { type: "integer", minimum: 0 }, slots: { type: "object" }, layout: { type: "object" }, style: { type: "object" }, decorations: { type: "array" } }, required: ["runId"] }, outputSchema: objectSchema },
32242
32283
  { name: "archive_run", title: "Archive slideshow run", description: "Hide a Run from default lists without deleting it.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { runId: { type: "string" } }, required: ["runId"] }, outputSchema: objectSchema },
32243
32284
  { name: "restore_run", title: "Restore slideshow run", description: "Restore an archived or trashed Run.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { runId: { type: "string" } }, required: ["runId"] }, outputSchema: objectSchema },
32244
- { name: "trash_run", title: "Trash slideshow run", description: "Move a Run to trash (recoverable for 30 days).", effect: "explicit-approval", inputSchema: { ...objectSchema, properties: { runId: { type: "string" } }, required: ["runId"] }, outputSchema: objectSchema },
32245
- { name: "render_run", title: "Render slideshow run", description: "Render a topology-locked Run as an atomic PNG set.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { runId: { type: "string" }, idempotencyKey: { type: "string", minLength: 1, maxLength: 128 } }, required: ["runId", "idempotencyKey"] }, outputSchema: objectSchema }
32285
+ { name: "trash_run", title: "Delete slideshow run", description: "Permanently delete a Slideshow Run (hard delete; not recoverable).", effect: "explicit-approval", inputSchema: { ...objectSchema, properties: { runId: { type: "string" } }, required: ["runId"] }, outputSchema: objectSchema },
32286
+ { name: "render_run", title: "Render slideshow run", description: "Retired. PNG packs come from Studio Download in the browser, not hosted render jobs.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { runId: { type: "string" }, idempotencyKey: { type: "string", minLength: 1, maxLength: 128 } }, required: ["runId", "idempotencyKey"] }, outputSchema: objectSchema }
32246
32287
  ];
32247
32288
  slideshowResourceTemplates = [
32248
32289
  { uriTemplate: "slideshow://runs/{runId}", name: "Slideshow Run", description: "Owner-scoped Run JSON snapshot", mimeType: "application/json" }
@@ -32277,7 +32318,7 @@ var init_src5 = __esm({
32277
32318
  case "trash_run":
32278
32319
  return { ok: true, data: await this.client.trashSlideshowRun(String(args.runId)), meta: m };
32279
32320
  case "render_run":
32280
- return { ok: true, data: await this.client.renderSlideshowRun(String(args.runId), { outputFormats: args.outputFormats ?? ["png"], idempotencyKey: String(args.idempotencyKey) }), meta: m };
32321
+ return { ok: false, error: { code: "HEADLESS_RENDER_DISABLED", message: "PNG packs come from Studio Download, not CLI/MCP hosted jobs", retryable: false, suggestedAction: "Open the Slideshow in Studio and click Download" }, meta: m };
32281
32322
  default:
32282
32323
  return { ok: false, error: { code: "UNKNOWN_TOOL", message: `Unknown tool: ${toolName}`, retryable: false }, meta: m };
32283
32324
  }
@@ -32309,13 +32350,20 @@ async function serveUnifiedStdioMcp(client) {
32309
32350
  resourceTemplates: unifiedResourceTemplates
32310
32351
  }, { backendName: "gateway" });
32311
32352
  }
32312
- var removedTools, templateNames, slideshowNames, brandTools, rename, templateTools, slideshowTools, imageTools, unifiedToolRegistry, unifiedResourceTemplates, UnifiedMcpBackend;
32353
+ var HEADLESS_PNG_DELIVERY, HEADLESS_TOOLS, removedTools, templateNames, slideshowNames, brandTools, rename, templateTools, slideshowTools, imageTools, unifiedToolRegistry, unifiedResourceTemplates, UnifiedMcpBackend;
32313
32354
  var init_src6 = __esm({
32314
32355
  "../../packages/mcp-server/src/index.ts"() {
32315
32356
  "use strict";
32316
32357
  init_src3();
32317
32358
  init_src4();
32318
32359
  init_src5();
32360
+ HEADLESS_PNG_DELIVERY = {
32361
+ code: "HEADLESS_RENDER_DISABLED",
32362
+ message: "PNG packs come from Studio Download, not CLI/MCP hosted jobs",
32363
+ retryable: false,
32364
+ suggestedAction: "Open the Slideshow in Studio and click Download"
32365
+ };
32366
+ HEADLESS_TOOLS = /* @__PURE__ */ new Set(["create_template_render_job", "render_slideshow_run"]);
32319
32367
  removedTools = /* @__PURE__ */ new Set(["publish_run", "unpublish_run"]);
32320
32368
  templateNames = {
32321
32369
  create_draft: "create_template_draft",
@@ -32369,6 +32417,13 @@ var init_src6 = __esm({
32369
32417
  this.slideshow = new SlideshowMcpBackend(client);
32370
32418
  }
32371
32419
  invoke(toolName, args) {
32420
+ if (HEADLESS_TOOLS.has(toolName)) {
32421
+ return Promise.resolve({
32422
+ ok: false,
32423
+ error: { ...HEADLESS_PNG_DELIVERY },
32424
+ meta: { requestId: `mcp_${Date.now().toString(36)}`, runtimeVersion: "0.0.0", schemaVersion: 1, toolContractVersion: "2026-09-04" }
32425
+ });
32426
+ }
32372
32427
  const templateTool = templateTools.find((tool) => tool.name === toolName);
32373
32428
  if (templateTool) return this.template.invoke(Object.entries(templateNames).find(([, name]) => name === toolName)?.[0] ?? toolName, args);
32374
32429
  const slideshowTool = slideshowTools.find((tool) => tool.name === toolName);
@@ -32452,11 +32507,10 @@ var init_v1_mcp = __esm({
32452
32507
 
32453
32508
  // src/v1-cli.ts
32454
32509
  init_src2();
32455
- import { cp, mkdtemp, readFile, rename as rename2, rm, writeFile, mkdir, chmod, copyFile } from "fs/promises";
32456
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
32457
- import { createHash } from "crypto";
32458
- import { homedir as homedir2, tmpdir } from "os";
32459
- import { join as join3 } from "path";
32510
+ import { cp, readFile, writeFile, mkdir, chmod, copyFile } from "fs/promises";
32511
+ import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
32512
+ import { homedir as homedir3 } from "os";
32513
+ import { join as join4, resolve as resolve2 } from "path";
32460
32514
  import { Command } from "commander";
32461
32515
 
32462
32516
  // src/runtime/mcp-hosts.ts
@@ -32581,24 +32635,430 @@ function resolveDefaultSkillsSource(moduleUrl) {
32581
32635
  return bundled;
32582
32636
  }
32583
32637
 
32638
+ // src/engine.ts
32639
+ import { spawn, spawnSync } from "child_process";
32640
+ import {
32641
+ copyFileSync,
32642
+ existsSync as existsSync3,
32643
+ mkdirSync as mkdirSync2,
32644
+ readdirSync,
32645
+ readFileSync as readFileSync2,
32646
+ statSync,
32647
+ writeFileSync as writeFileSync2
32648
+ } from "fs";
32649
+ import net from "net";
32650
+ import { homedir as homedir2 } from "os";
32651
+ import { basename, dirname as dirname3, join as join3, resolve } from "path";
32652
+ var ENGINE_ROOT_NAME = "Content Engine";
32653
+ var WORKBENCH_PORT = 8765;
32654
+ var WORKBENCH_URL = `http://127.0.0.1:${WORKBENCH_PORT}/workbench/`;
32655
+ var WORKBENCH_FILES = ["template.html", "build.py", "serve.py"];
32656
+ function contentEnginePackageRoot(moduleUrl) {
32657
+ const pkg = cliPackageRoot(moduleUrl);
32658
+ const bundled = join3(pkg, "content-engine");
32659
+ if (existsSync3(join3(bundled, "starter")))
32660
+ return bundled;
32661
+ const sibling = join3(pkg, "content-engine");
32662
+ if (existsSync3(join3(sibling, "starter")))
32663
+ return sibling;
32664
+ return bundled;
32665
+ }
32666
+ function starterDir(moduleUrl) {
32667
+ return join3(contentEnginePackageRoot(moduleUrl), "starter");
32668
+ }
32669
+ function workbenchSrc(moduleUrl) {
32670
+ return join3(contentEnginePackageRoot(moduleUrl), "workbench");
32671
+ }
32672
+ function scriptsDir(moduleUrl) {
32673
+ return join3(contentEnginePackageRoot(moduleUrl), "scripts");
32674
+ }
32675
+ function isDir(path) {
32676
+ return existsSync3(path) && statSync(path).isDirectory();
32677
+ }
32678
+ function isEngine(path) {
32679
+ return isDir(path) && existsSync3(join3(path, "CLAUDE.md")) && isDir(join3(path, "runs"));
32680
+ }
32681
+ function resolveEngine(rootOrEngine) {
32682
+ const absolute = resolve(rootOrEngine);
32683
+ if (isEngine(absolute) || basename(absolute) === ENGINE_ROOT_NAME)
32684
+ return absolute;
32685
+ return join3(absolute, ENGINE_ROOT_NAME);
32686
+ }
32687
+ function walkFiles(dir) {
32688
+ const out = [];
32689
+ if (!existsSync3(dir))
32690
+ return out;
32691
+ for (const name of readdirSync(dir)) {
32692
+ if (name === ".DS_Store")
32693
+ continue;
32694
+ const path = join3(dir, name);
32695
+ if (statSync(path).isDirectory())
32696
+ out.push(...walkFiles(path));
32697
+ else
32698
+ out.push(path);
32699
+ }
32700
+ return out;
32701
+ }
32702
+ function engineJson() {
32703
+ return `${JSON.stringify({
32704
+ schemaVersion: "content-engine.v1",
32705
+ locale: "en",
32706
+ root: ENGINE_ROOT_NAME,
32707
+ paths: {
32708
+ runs: "runs",
32709
+ exports: "exports",
32710
+ topics: "topics",
32711
+ needs: "needs",
32712
+ products: "products",
32713
+ recommendations: "recommendations",
32714
+ swipe: "swipe",
32715
+ profiles: "profiles",
32716
+ inbox: "inbox",
32717
+ accounts: "accounts",
32718
+ raw: "raw",
32719
+ published: "published",
32720
+ wiki: "wiki",
32721
+ platforms: "platforms",
32722
+ workbench: "workbench"
32723
+ }
32724
+ }, null, 2)}
32725
+ `;
32726
+ }
32727
+ function starterFiles(moduleUrl) {
32728
+ const root = starterDir(moduleUrl);
32729
+ const files = {};
32730
+ for (const path of walkFiles(root)) {
32731
+ const relativePath = path.slice(root.length + 1).split("\\").join("/");
32732
+ files[relativePath] = readFileSync2(path, "utf8");
32733
+ }
32734
+ files["engine.json"] = engineJson();
32735
+ return files;
32736
+ }
32737
+ function inventory(engine, moduleUrl) {
32738
+ const files = starterFiles(moduleUrl);
32739
+ const missing = [];
32740
+ const existing = [];
32741
+ for (const relative of Object.keys(files)) {
32742
+ const path = join3(engine, relative);
32743
+ (existsSync3(path) ? existing : missing).push(relative);
32744
+ }
32745
+ return {
32746
+ engine,
32747
+ engineExists: isDir(engine),
32748
+ rootName: ENGINE_ROOT_NAME,
32749
+ missing,
32750
+ existing,
32751
+ starterComplete: missing.length === 0
32752
+ };
32753
+ }
32754
+ function tableCell(md, field) {
32755
+ const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32756
+ const match = md.match(new RegExp(`^\\|\\s*(?:\\*\\*)?${escaped}(?:\\*\\*)?\\s*\\|\\s*([^|]*?)\\|`, "im"));
32757
+ return match ? match[1].trim() : null;
32758
+ }
32759
+ function isFilledCell(value) {
32760
+ if (value == null)
32761
+ return false;
32762
+ const text = value.trim();
32763
+ return text.length > 0 && !/\(fill\)/i.test(text);
32764
+ }
32765
+ function headingSection(md, title) {
32766
+ const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32767
+ const match = md.match(new RegExp(`^##\\s+${escaped}\\s*$`, "im"));
32768
+ if (!match || match.index == null)
32769
+ return null;
32770
+ const rest = md.slice(match.index + match[0].length);
32771
+ const next = rest.search(/^##\s+/m);
32772
+ return (next < 0 ? rest : rest.slice(0, next)).trim();
32773
+ }
32774
+ function isFilledSection(body) {
32775
+ if (body == null)
32776
+ return false;
32777
+ const cleaned = body.replace(/\(fill\)/gi, "").replace(/^[-*]\s*$/gm, "").replace(/^\d+\.\s*$/gm, "").trim();
32778
+ return cleaned.length > 0;
32779
+ }
32780
+ function activeProfileId(engine) {
32781
+ const path = join3(engine, "accounts", "_index.md");
32782
+ if (!existsSync3(path))
32783
+ return null;
32784
+ for (const line of readFileSync2(path, "utf8").split("\n")) {
32785
+ if (!line.startsWith("|"))
32786
+ continue;
32787
+ const cells = line.split("|").slice(1, -1).map((cell) => cell.trim());
32788
+ if (cells.length < 7)
32789
+ continue;
32790
+ if (/^-+$/.test(cells[0]) || /^handle$/i.test(cells[0]) || /^column$/i.test(cells[0]))
32791
+ continue;
32792
+ if (cells[6].toLowerCase() === "active" && cells[1])
32793
+ return cells[1];
32794
+ }
32795
+ return null;
32796
+ }
32797
+ function countNeedIds(md) {
32798
+ let count = 0;
32799
+ for (const line of md.split("\n")) {
32800
+ if (/^\|\s*N-\d{8}-\d+/.test(line))
32801
+ count += 1;
32802
+ }
32803
+ return count;
32804
+ }
32805
+ function checkKnowledge(engine) {
32806
+ const missing = [];
32807
+ const profileId = activeProfileId(engine);
32808
+ if (!profileId)
32809
+ missing.push("accounts/_index.md");
32810
+ const profile = profileId || "default";
32811
+ const voiceRel = `profiles/${profile}/voice.md`;
32812
+ const voicePath = join3(engine, voiceRel);
32813
+ if (!existsSync3(voicePath)) {
32814
+ missing.push(voiceRel);
32815
+ } else {
32816
+ const voice = readFileSync2(voicePath, "utf8");
32817
+ for (const field of ["Role", "What I build", "Point of view"]) {
32818
+ if (!isFilledCell(tableCell(voice, field))) {
32819
+ missing.push(voiceRel);
32820
+ break;
32821
+ }
32822
+ }
32823
+ }
32824
+ const boundariesRel = `profiles/${profile}/boundaries.md`;
32825
+ const boundariesPath = join3(engine, boundariesRel);
32826
+ if (!existsSync3(boundariesPath)) {
32827
+ missing.push(boundariesRel);
32828
+ } else {
32829
+ const boundaries = readFileSync2(boundariesPath, "utf8");
32830
+ if (!isFilledSection(headingSection(boundaries, "Not covering")) || !isFilledSection(headingSection(boundaries, "Never claim")))
32831
+ missing.push(boundariesRel);
32832
+ }
32833
+ const needsDir = join3(engine, "needs");
32834
+ if (isDir(needsDir)) {
32835
+ const needsRel = "needs/_index.md";
32836
+ const needsPath = join3(engine, needsRel);
32837
+ if (!existsSync3(needsPath) || countNeedIds(readFileSync2(needsPath, "utf8")) < 3)
32838
+ missing.push(needsRel);
32839
+ }
32840
+ return { valid: missing.length === 0, missing };
32841
+ }
32842
+ function validateEngine(rootOrEngine, moduleUrl, { strict = false, requireKnowledge = false } = {}) {
32843
+ const engine = resolveEngine(rootOrEngine);
32844
+ const parent = resolve(engine, "..");
32845
+ const errors = [];
32846
+ if (!existsSync3(parent) || !statSync(parent).isDirectory()) {
32847
+ errors.push("Parent directory does not exist");
32848
+ return { ...inventory(engine, moduleUrl), valid: false, errors, created: [] };
32849
+ }
32850
+ const result = inventory(engine, moduleUrl);
32851
+ if (existsSync3(engine) && !isDir(engine))
32852
+ errors.push("Content Engine exists but is not a directory");
32853
+ if (strict) {
32854
+ if (!result.engineExists)
32855
+ errors.push("Content Engine/ folder does not exist");
32856
+ else if (result.missing.length > 0)
32857
+ errors.push(`Starter incomplete: ${result.missing.length} file(s) missing (use engine init --yes to add only missing files)`);
32858
+ }
32859
+ const knowledge = requireKnowledge ? checkKnowledge(engine) : void 0;
32860
+ if (knowledge && !knowledge.valid)
32861
+ errors.push(`Knowledge gate failed: ${knowledge.missing.join(", ")}`);
32862
+ return { ...result, valid: errors.length === 0, errors, created: [], ...knowledge ? { knowledge } : {} };
32863
+ }
32864
+ function python3Bin() {
32865
+ const result = spawnSync("python3", ["--version"], { encoding: "utf8" });
32866
+ return result.status === 0 ? "python3" : "";
32867
+ }
32868
+ function ensureWorkbench(engine, moduleUrl, { update = false } = {}) {
32869
+ const destDir = join3(engine, "workbench");
32870
+ mkdirSync2(destDir, { recursive: true });
32871
+ const copied = [];
32872
+ const updated = [];
32873
+ const skipped = [];
32874
+ const src = workbenchSrc(moduleUrl);
32875
+ for (const name of WORKBENCH_FILES) {
32876
+ const dest = join3(destDir, name);
32877
+ const from = join3(src, name);
32878
+ if (existsSync3(dest) && !update) {
32879
+ skipped.push(name);
32880
+ continue;
32881
+ }
32882
+ const existed = existsSync3(dest);
32883
+ copyFileSync(from, dest);
32884
+ if (existed)
32885
+ updated.push(name);
32886
+ else
32887
+ copied.push(name);
32888
+ }
32889
+ return { destDir, copied, updated, skipped };
32890
+ }
32891
+ function buildWorkbench(engine) {
32892
+ const python = python3Bin();
32893
+ if (!python)
32894
+ return { ok: false, code: "PYTHON3_REQUIRED", message: "python3 is required for the workbench. Install Python 3 and retry." };
32895
+ const script = join3(engine, "workbench", "build.py");
32896
+ if (!existsSync3(script))
32897
+ return { ok: false, code: "WORKBENCH_MISSING", message: "workbench/build.py is missing. Re-run easysociable engine init --yes." };
32898
+ const result = spawnSync(python, [script], { encoding: "utf8", cwd: engine });
32899
+ if (result.status !== 0)
32900
+ return { ok: false, code: "WORKBENCH_BUILD_FAILED", message: result.stderr.trim() || result.stdout.trim() || "workbench build failed" };
32901
+ return { ok: true, message: result.stdout.trim() };
32902
+ }
32903
+ function portInUse(port = WORKBENCH_PORT) {
32904
+ return new Promise((resolvePort) => {
32905
+ const socket = net.connect({ port, host: "127.0.0.1" }, () => {
32906
+ socket.end();
32907
+ resolvePort(true);
32908
+ });
32909
+ socket.on("error", () => resolvePort(false));
32910
+ });
32911
+ }
32912
+ function openUrl(url2) {
32913
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
32914
+ const args = process.platform === "win32" ? ["/c", "start", url2] : [url2];
32915
+ spawn(command, args, { stdio: "ignore", detached: true }).unref();
32916
+ }
32917
+ async function startWorkbench(engine, moduleUrl, { noOpen = false } = {}) {
32918
+ const python = python3Bin();
32919
+ if (!python)
32920
+ return { ok: false, code: "PYTHON3_REQUIRED", message: "python3 is required for the workbench. Install Python 3 and retry." };
32921
+ ensureWorkbench(engine, moduleUrl, { update: true });
32922
+ const built = buildWorkbench(engine);
32923
+ if (!built.ok)
32924
+ return built;
32925
+ const url2 = WORKBENCH_URL;
32926
+ if (await portInUse()) {
32927
+ if (!noOpen && process.env.EASYSOCIABLE_NO_OPEN !== "true")
32928
+ openUrl(url2);
32929
+ return { ok: true, alreadyRunning: true, url: url2, build: built.message };
32930
+ }
32931
+ const args = [join3(engine, "workbench", "serve.py")];
32932
+ if (noOpen)
32933
+ args.push("--no-open");
32934
+ const child = spawn(python, args, { cwd: engine, detached: true, stdio: "ignore" });
32935
+ child.unref();
32936
+ return { ok: true, alreadyRunning: false, url: url2, pid: child.pid, build: built.message };
32937
+ }
32938
+ async function initEngine(rootOrEngine, moduleUrl, {
32939
+ yes = false,
32940
+ noOpen = false,
32941
+ serve = false
32942
+ } = {}) {
32943
+ const engine = resolveEngine(rootOrEngine);
32944
+ if (!yes) {
32945
+ const preview = validateEngine(engine, moduleUrl);
32946
+ return {
32947
+ ...preview,
32948
+ action: "preview",
32949
+ next: "Review missing files and re-run with --yes"
32950
+ };
32951
+ }
32952
+ const parent = resolve(engine, "..");
32953
+ if (!existsSync3(parent) || !statSync(parent).isDirectory()) {
32954
+ return {
32955
+ engine,
32956
+ action: "applied",
32957
+ valid: false,
32958
+ code: "PARENT_MISSING",
32959
+ errors: ["Parent directory does not exist"],
32960
+ created: []
32961
+ };
32962
+ }
32963
+ mkdirSync2(engine, { recursive: true });
32964
+ const report = inventory(engine, moduleUrl);
32965
+ const created = [];
32966
+ const files = starterFiles(moduleUrl);
32967
+ for (const relative of report.missing) {
32968
+ const path = join3(engine, relative);
32969
+ mkdirSync2(dirname3(path), { recursive: true });
32970
+ writeFileSync2(path, files[relative], { encoding: "utf8", flag: "wx" });
32971
+ created.push(relative);
32972
+ }
32973
+ const workbench = ensureWorkbench(engine, moduleUrl);
32974
+ const python = python3Bin();
32975
+ if (!python) {
32976
+ return {
32977
+ ...validateEngine(engine, moduleUrl),
32978
+ created,
32979
+ workbench,
32980
+ action: "applied",
32981
+ valid: false,
32982
+ code: "PYTHON3_REQUIRED",
32983
+ errors: ["python3 is required for the workbench. Install Python 3 and retry. Starter files were written; workbench was not started."]
32984
+ };
32985
+ }
32986
+ const built = buildWorkbench(engine);
32987
+ const started = serve ? await startWorkbench(engine, moduleUrl, { noOpen }) : { skipped: true, build: built };
32988
+ return {
32989
+ ...validateEngine(engine, moduleUrl),
32990
+ created,
32991
+ workbench,
32992
+ workbenchBuild: built,
32993
+ workbenchStart: started,
32994
+ action: "applied"
32995
+ };
32996
+ }
32997
+ function detectEngines() {
32998
+ const extra = (process.env.EASYSOCIABLE_ENGINE_SEARCH_ROOTS || "").split(process.platform === "win32" ? ";" : ":").map((item) => item.trim()).filter(Boolean);
32999
+ const home = homedir2();
33000
+ const roots = [...extra, join3(home, "Documents"), home];
33001
+ const found = [];
33002
+ for (const root of roots) {
33003
+ if (!isDir(root))
33004
+ continue;
33005
+ const direct = join3(root, ENGINE_ROOT_NAME);
33006
+ if (isEngine(direct) || isDir(direct))
33007
+ found.push(direct);
33008
+ try {
33009
+ for (const name of readdirSync(root)) {
33010
+ const child = join3(root, name, ENGINE_ROOT_NAME);
33011
+ if (isDir(child))
33012
+ found.push(child);
33013
+ }
33014
+ } catch {
33015
+ continue;
33016
+ }
33017
+ }
33018
+ return [...new Set(found)];
33019
+ }
33020
+ function validateRun(moduleUrl, args) {
33021
+ const script = join3(scriptsDir(moduleUrl), "validate-run.mjs");
33022
+ if (!existsSync3(script))
33023
+ throw new Error(`Missing validate-run script at ${script}`);
33024
+ const argv = ["--run", args.run];
33025
+ if (args.requirePack)
33026
+ argv.push("--require-pack");
33027
+ if (args.requireExport)
33028
+ argv.push("--require-export");
33029
+ if (args.exportDir)
33030
+ argv.push("--export", args.exportDir);
33031
+ if (args.requirePageTemplateId)
33032
+ argv.push("--require-page-template-id");
33033
+ if (args.json !== false)
33034
+ argv.push("--json");
33035
+ const result = spawnSync(process.execPath, [script, ...argv], { encoding: "utf8" });
33036
+ return {
33037
+ status: result.status ?? 1,
33038
+ stdout: result.stdout,
33039
+ stderr: result.stderr
33040
+ };
33041
+ }
33042
+
32584
33043
  // src/v1-cli.ts
32585
33044
  var CLI_MODULE_URL = import.meta.url;
32586
33045
  function readCliVersion() {
32587
33046
  try {
32588
- const pkg = JSON.parse(readFileSync2(join3(cliPackageRoot(CLI_MODULE_URL), "package.json"), "utf8"));
33047
+ const pkg = JSON.parse(readFileSync3(join4(cliPackageRoot(CLI_MODULE_URL), "package.json"), "utf8"));
32589
33048
  return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
32590
33049
  } catch {
32591
33050
  return "0.0.0";
32592
33051
  }
32593
33052
  }
32594
- var credentialPath = join3(homedir2(), ".config", "easysociable", "credentials.json");
33053
+ var HEADLESS_RENDER_DISABLED = "PNG delivery is Studio Download (browser ZIP). CLI/MCP hosted PNG jobs are retired. Open the Run in Studio and use Download.";
33054
+ var credentialPath = join4(homedir3(), ".config", "easysociable", "credentials.json");
32595
33055
  function resolveInstallSkillIds(options) {
32596
33056
  return parseInstallSkills(typeof options.skills === "string" ? options.skills : void 0);
32597
33057
  }
32598
33058
  function readStoredCredentials() {
32599
33059
  try {
32600
- if (!existsSync3(credentialPath)) return null;
32601
- const parsed = JSON.parse(readFileSync2(credentialPath, "utf8"));
33060
+ if (!existsSync4(credentialPath)) return null;
33061
+ const parsed = JSON.parse(readFileSync3(credentialPath, "utf8"));
32602
33062
  if (typeof parsed.apiKey !== "string" || typeof parsed.baseUrl !== "string") return null;
32603
33063
  return { apiKey: parsed.apiKey, baseUrl: parsed.baseUrl };
32604
33064
  } catch {
@@ -32606,7 +33066,7 @@ function readStoredCredentials() {
32606
33066
  }
32607
33067
  }
32608
33068
  async function writeStoredCredentials(credentials) {
32609
- await mkdir(join3(homedir2(), ".config", "easysociable"), { recursive: true, mode: 448 });
33069
+ await mkdir(join4(homedir3(), ".config", "easysociable"), { recursive: true, mode: 448 });
32610
33070
  await writeFile(credentialPath, `${JSON.stringify(credentials, null, 2)}
32611
33071
  `, { mode: 384 });
32612
33072
  await chmod(credentialPath, 384);
@@ -32622,6 +33082,13 @@ function print(value, json2) {
32622
33082
  else process.stdout.write(`${JSON.stringify(value, null, 2)}
32623
33083
  `);
32624
33084
  }
33085
+ function commandWantsJson(command) {
33086
+ const local = command.opts();
33087
+ if (local.json === true)
33088
+ return true;
33089
+ const withGlobals = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {};
33090
+ return withGlobals.json === true;
33091
+ }
32625
33092
  function clientFromEnv() {
32626
33093
  const stored = readStoredCredentials();
32627
33094
  const allowEnv = process.env.EASYSOCIABLE_ALLOW_ENV_CREDENTIALS === "true";
@@ -32638,8 +33105,16 @@ function clientFromEnv() {
32638
33105
  function jsonOption(command) {
32639
33106
  return command.option("--json", "Emit a JSON envelope");
32640
33107
  }
33108
+ function defaultEngineRoot() {
33109
+ try {
33110
+ if (existsSync4("/workspace") && statSync2("/workspace").isDirectory())
33111
+ return "/workspace";
33112
+ } catch {
33113
+ }
33114
+ return process.cwd();
33115
+ }
32641
33116
  function installHome(options) {
32642
- return typeof options.home === "string" && options.home ? String(options.home) : homedir2();
33117
+ return typeof options.home === "string" && options.home ? String(options.home) : homedir3();
32643
33118
  }
32644
33119
  function installSource(options) {
32645
33120
  if (typeof options.source === "string" && options.source)
@@ -32650,17 +33125,17 @@ function installSource(options) {
32650
33125
  }
32651
33126
  function detectedHosts(home) {
32652
33127
  const candidates = [
32653
- { host: "codex", dir: join3(home, ".codex") },
32654
- { host: "claude", dir: join3(home, ".claude") }
33128
+ { host: "codex", dir: join4(home, ".codex") },
33129
+ { host: "claude", dir: join4(home, ".claude") }
32655
33130
  ];
32656
- return candidates.filter((item) => existsSync3(item.dir)).map((item) => ({ host: item.host, configPath: join3(item.dir, "mcp.json"), skillsPath: join3(item.dir, "skills") }));
33131
+ return candidates.filter((item) => existsSync4(item.dir)).map((item) => ({ host: item.host, configPath: join4(item.dir, "mcp.json"), skillsPath: join4(item.dir, "skills") }));
32657
33132
  }
32658
33133
  async function planInstallation(options) {
32659
33134
  const home = installHome(options);
32660
33135
  const source = installSource(options);
32661
33136
  const hosts = detectedHosts(home);
32662
33137
  const skillNames = resolveInstallSkillIds(options);
32663
- const missingSkills = skillNames.filter((name) => !existsSync3(join3(source, name)));
33138
+ const missingSkills = skillNames.filter((name) => !existsSync4(join4(source, name)));
32664
33139
  return {
32665
33140
  source,
32666
33141
  home,
@@ -32669,7 +33144,7 @@ async function planInstallation(options) {
32669
33144
  missingSkills,
32670
33145
  changes: hosts.flatMap((host) => [
32671
33146
  `merge ${host.configPath} with one easysociable MCP registration`,
32672
- ...skillNames.map((name) => `install ${join3(host.skillsPath, name)}`)
33147
+ ...skillNames.map((name) => `install ${join4(host.skillsPath, name)}`)
32673
33148
  ])
32674
33149
  };
32675
33150
  }
@@ -32685,7 +33160,7 @@ async function applyInstallation(options) {
32685
33160
  const backupSuffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
32686
33161
  const backups = [];
32687
33162
  for (const host of detectedHosts(String(plan.home))) {
32688
- if (existsSync3(host.configPath)) {
33163
+ if (existsSync4(host.configPath)) {
32689
33164
  const backupPath = `${host.configPath}.bak.${backupSuffix}`;
32690
33165
  await copyFile(host.configPath, backupPath);
32691
33166
  backups.push(backupPath);
@@ -32693,31 +33168,22 @@ async function applyInstallation(options) {
32693
33168
  await installHostConfig({ host: host.host, dataDir: "", configPath: host.configPath });
32694
33169
  await mkdir(host.skillsPath, { recursive: true, mode: 448 });
32695
33170
  for (const skill of resolveInstallSkillIds(options))
32696
- await cp(join3(String(plan.source), skill), join3(host.skillsPath, skill), { recursive: true, force: false, errorOnExist: false });
33171
+ await cp(join4(String(plan.source), skill), join4(host.skillsPath, skill), { recursive: true, force: false, errorOnExist: false });
33172
+ }
33173
+ if (options.json !== true) {
33174
+ process.stdout.write(`Installed skills your Agent will use:
33175
+ easysociable-slideshow \u2014 turns a draft into slideshow pages you preview and download here
33176
+ easysociable-template \u2014 creates page templates
33177
+ `);
32697
33178
  }
32698
33179
  return { ...plan, applied: true, backups };
32699
33180
  }
32700
- async function readJsonArgument(value) {
32701
- if (existsSync3(value))
32702
- return JSON.parse(await readFile(value, "utf8"));
32703
- return JSON.parse(value);
32704
- }
32705
33181
  function mimeTypeFor(fileName) {
32706
33182
  const extension = fileName.toLowerCase().split(".").pop();
32707
33183
  if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
32708
33184
  if (extension === "webp") return "image/webp";
32709
33185
  return "image/png";
32710
33186
  }
32711
- function renderSlots(value) {
32712
- if (!value || typeof value !== "object" || Array.isArray(value))
32713
- throw new Error("Render slots must be a JSON object");
32714
- const entries = Object.entries(value);
32715
- for (const [key, item] of entries) {
32716
- if (!(typeof item === "string" || typeof item === "number" || item === null))
32717
- throw new Error(`Render slot ${key} must be a string, number or null`);
32718
- }
32719
- return Object.fromEntries(entries);
32720
- }
32721
33187
  function withClient(action) {
32722
33188
  return async (...args) => {
32723
33189
  const command = args.at(-1);
@@ -32732,69 +33198,6 @@ function withClient(action) {
32732
33198
  }
32733
33199
  };
32734
33200
  }
32735
- async function exportManifest(client, jobId, output) {
32736
- const job = await client.getRenderJob(jobId);
32737
- if (job.status !== "completed" || !job.manifest)
32738
- throw new Error(`Render job ${jobId} is not completed or has no manifest`);
32739
- const staging = await mkdtemp(join3(tmpdir(), "easysociable-export-"));
32740
- try {
32741
- for (const [index, artifact] of job.manifest.artifacts.entries()) {
32742
- const response = await fetch(artifact.url);
32743
- if (!response.ok) throw new Error(`Artifact download failed (${response.status})`);
32744
- const bytes = Buffer.from(await response.arrayBuffer());
32745
- const checksum = createHash("sha256").update(bytes).digest("hex");
32746
- if (checksum !== artifact.sha256) throw new Error(`Artifact checksum mismatch for page ${index + 1}`);
32747
- await writeFile(join3(staging, `${String(index + 1).padStart(2, "0")}.png`), bytes);
32748
- }
32749
- await writeFile(join3(staging, "manifest.json"), `${JSON.stringify(job.manifest, null, 2)}
32750
- `);
32751
- await rm(output, { recursive: true, force: true });
32752
- await rename2(staging, output);
32753
- return { jobId, output, pages: job.manifest.artifacts.length, manifest: job.manifest };
32754
- } catch (error52) {
32755
- await rm(staging, { recursive: true, force: true });
32756
- throw error52;
32757
- }
32758
- }
32759
- async function exportSlideshowRun(client, runId, output) {
32760
- const manifest = await client.getSlideshowRunManifest(runId);
32761
- const staging = await mkdtemp(join3(tmpdir(), "easysociable-slideshow-export-"));
32762
- try {
32763
- const pages = [];
32764
- for (const artifact of manifest.artifacts) {
32765
- const bytes = Buffer.from(await client.downloadSlideshowArtifact(runId, artifact.pageIndex));
32766
- const checksum = createHash("sha256").update(bytes).digest("hex");
32767
- if (checksum !== artifact.sha256)
32768
- throw new Error(`Artifact checksum mismatch for page ${artifact.pageIndex + 1}`);
32769
- const role = artifact.role || `page${artifact.pageIndex}`;
32770
- const fileName = `${String(artifact.pageIndex + 1).padStart(2, "0")}-${role}.png`;
32771
- await writeFile(join3(staging, fileName), bytes);
32772
- pages.push({
32773
- pageIndex: artifact.pageIndex,
32774
- role,
32775
- file: fileName,
32776
- bytes: bytes.length,
32777
- sha256: checksum,
32778
- width: artifact.width,
32779
- height: artifact.height
32780
- });
32781
- }
32782
- const localManifest = {
32783
- schema: "slideshow-hosted-export.v1",
32784
- runId: manifest.runId,
32785
- deckJobId: manifest.deckJobId,
32786
- pages
32787
- };
32788
- await writeFile(join3(staging, "manifest.json"), `${JSON.stringify(localManifest, null, 2)}
32789
- `);
32790
- await rm(output, { recursive: true, force: true });
32791
- await rename2(staging, output);
32792
- return { runId, output, pages: pages.length, manifest: localManifest };
32793
- } catch (error52) {
32794
- await rm(staging, { recursive: true, force: true });
32795
- throw error52;
32796
- }
32797
- }
32798
33201
  function addCrudCommands(parent, resource, routes) {
32799
33202
  jsonOption(parent.command("list").description(`List ${resource}`)).action(withClient(async (client) => client.requestJson("GET", routes.list)));
32800
33203
  jsonOption(parent.command("get").requiredOption("--id <id>").description(`Get ${resource}`)).action(withClient(async (client, opts) => client.requestJson("GET", routes.get.replace(":id", String(opts.id)))));
@@ -32821,6 +33224,80 @@ function buildV1Cli() {
32821
33224
  const result = opts.yes === true ? await applyInstallation(opts) : { ...plan, applied: false, next: "Review changes and re-run with --yes" };
32822
33225
  print(result, Boolean(opts.json));
32823
33226
  });
33227
+ const engine = program.command("engine").description("Create and operate a local Content Engine + workbench");
33228
+ jsonOption(engine.command("detect").description("Find Content Engine folders under search roots")).action((...args) => {
33229
+ const command = args.at(-1);
33230
+ print({ action: "detect", engines: detectEngines() }, commandWantsJson(command));
33231
+ });
33232
+ jsonOption(engine.command("init").description("Scaffold Content Engine starter files (never overwrites existing)").option("--root <path>", "Parent directory or Content Engine path", defaultEngineRoot()).option("--yes", "Write missing starter files").option("--serve", "Also start the workbench server").option("--no-open", "Do not open a browser when starting the workbench")).action(async (...args) => {
33233
+ const command = args.at(-1);
33234
+ const opts = command.opts();
33235
+ const result = await initEngine(String(opts.root), CLI_MODULE_URL, {
33236
+ yes: opts.yes === true,
33237
+ noOpen: opts.noOpen === true,
33238
+ serve: opts.serve === true
33239
+ });
33240
+ print(result, commandWantsJson(command));
33241
+ if (opts.yes === true && (result.valid === false || result.code)) {
33242
+ const errors = result.errors;
33243
+ throw new Error(errors?.[0] ?? result.code ?? "engine init failed");
33244
+ }
33245
+ });
33246
+ jsonOption(engine.command("validate").description("Validate engine starter completeness or a run folder").option("--root <path>", "Parent directory or Content Engine path").option("--run <path>", "Run directory to validate").option("--require-pack", "Require pack-*.json on the run").option("--require-export", "Require export directory").option("--export <path>", "Export directory for --require-export").option("--require-knowledge", "Require Identity (Role / What I build / POV) + boundaries; needs count only if needs/ exists")).action((...args) => {
33247
+ const command = args.at(-1);
33248
+ const opts = command.opts();
33249
+ if (opts.run) {
33250
+ if (opts.requireKnowledge === true) {
33251
+ const engineRoot = resolve2(String(opts.run), "..", "..");
33252
+ const knowledge = checkKnowledge(engineRoot);
33253
+ if (!knowledge.valid) {
33254
+ print({ valid: false, missing: knowledge.missing, knowledge, action: "validated" }, commandWantsJson(command));
33255
+ throw new Error(`Knowledge gate failed: ${knowledge.missing.join(", ")}`);
33256
+ }
33257
+ }
33258
+ const result = validateRun(CLI_MODULE_URL, {
33259
+ run: String(opts.run),
33260
+ requirePack: opts.requirePack === true,
33261
+ requireExport: opts.requireExport === true,
33262
+ exportDir: opts.export ? String(opts.export) : void 0,
33263
+ json: true
33264
+ });
33265
+ if (result.stdout)
33266
+ process.stdout.write(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}
33267
+ `);
33268
+ if (result.status !== 0)
33269
+ throw new Error(result.stderr.trim() || "engine validate --run failed");
33270
+ return;
33271
+ }
33272
+ if (!opts.root)
33273
+ throw new Error("Pass --root <path> or --run <path>.");
33274
+ const report = validateEngine(String(opts.root), CLI_MODULE_URL, {
33275
+ strict: true,
33276
+ requireKnowledge: opts.requireKnowledge === true
33277
+ });
33278
+ print({ ...report, action: "validated" }, commandWantsJson(command));
33279
+ if (!report.valid)
33280
+ throw new Error(report.errors[0] ?? "engine validate failed");
33281
+ });
33282
+ jsonOption(engine.command("serve").description("Sync workbench runtime from CLI, rebuild, start on :8765").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot()).option("--no-open", "Do not open a browser")).action(async (...args) => {
33283
+ const command = args.at(-1);
33284
+ const opts = command.opts();
33285
+ const enginePath = resolveEngine(String(opts.engine));
33286
+ const started = await startWorkbench(enginePath, CLI_MODULE_URL, { noOpen: opts.noOpen === true });
33287
+ print({ action: "serve", engine: enginePath, ...started }, commandWantsJson(command));
33288
+ if (!started.ok)
33289
+ throw new Error("message" in started ? String(started.message) : "engine serve failed");
33290
+ });
33291
+ jsonOption(engine.command("build").description("Sync workbench runtime from CLI, then rebuild workbench/index.html").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot())).action((...args) => {
33292
+ const command = args.at(-1);
33293
+ const opts = command.opts();
33294
+ const enginePath = resolveEngine(String(opts.engine));
33295
+ const workbench = ensureWorkbench(enginePath, CLI_MODULE_URL, { update: true });
33296
+ const built = buildWorkbench(enginePath);
33297
+ print({ action: "build", engine: enginePath, workbench, ...built }, commandWantsJson(command));
33298
+ if (!built.ok)
33299
+ throw new Error(built.message);
33300
+ });
32824
33301
  const auth = program.command("auth").description("Manage the shared EasySociable credential");
32825
33302
  jsonOption(auth.command("status").description("Show authentication status")).action(async (...args) => {
32826
33303
  const command = args.at(-1);
@@ -32860,6 +33337,13 @@ function buildV1Cli() {
32860
33337
  jsonOption(templates.command("archive").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.archiveDraft(String(opts.id))));
32861
33338
  jsonOption(templates.command("restore").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.restoreDraft(String(opts.id))));
32862
33339
  jsonOption(templates.command("delete").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.deleteDraft(String(opts.id))));
33340
+ const families = templates.command("families").description("Layout Family (multi-platform template identity)");
33341
+ jsonOption(families.command("get").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.getTemplateFamily(String(opts.id))));
33342
+ jsonOption(families.command("derive").requiredOption("--id <id>").option("--presets <list>", "Comma-separated presets e.g. 4:5,9:16")).action(withClient(async (client, opts) => {
33343
+ const presets = typeof opts.presets === "string" && opts.presets.trim() ? String(opts.presets).split(",").map((item) => item.trim()).filter(Boolean) : void 0;
33344
+ return client.deriveTemplateFamily(String(opts.id), { presets });
33345
+ }));
33346
+ jsonOption(families.command("migrate-legacy").description("Wrap pages missing familyId into single-variant families")).action(withClient(async (client) => client.migrateLegacyTemplateFamilies()));
32863
33347
  const icons = program.command("icons").description("Search the internal safe icon catalog");
32864
33348
  jsonOption(icons.command("search").requiredOption("--query <query>")).action(withClient(async (client, opts) => client.searchIcons({ query: String(opts.query) })));
32865
33349
  jsonOption(icons.command("get").requiredOption("--id <id>").requiredOption("--idempotency-key <key>")).action(withClient(async (client, opts) => client.getIcon({ iconId: String(opts.id), idempotencyKey: String(opts.idempotencyKey) })));
@@ -32877,8 +33361,12 @@ function buildV1Cli() {
32877
33361
  jsonOption(slideshows.command("archive").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.archiveSlideshowRun(String(opts.id))));
32878
33362
  jsonOption(slideshows.command("restore").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.restoreSlideshowRun(String(opts.id))));
32879
33363
  jsonOption(slideshows.command("delete").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.trashSlideshowRun(String(opts.id))));
32880
- jsonOption(slideshows.command("render").requiredOption("--id <id>").requiredOption("--idempotency-key <key>")).action(withClient(async (client, opts) => client.renderSlideshowRun(String(opts.id), { outputFormats: ["png"], idempotencyKey: String(opts.idempotencyKey) })));
32881
- jsonOption(slideshows.command("export").requiredOption("--id <id>").requiredOption("--output <path>")).action(withClient(async (client, opts) => exportSlideshowRun(client, String(opts.id), String(opts.output))));
33364
+ jsonOption(slideshows.command("render").description("Retired: PNG packs come from Studio Download").requiredOption("--id <id>").requiredOption("--idempotency-key <key>")).action(withClient(async () => {
33365
+ throw new Error(HEADLESS_RENDER_DISABLED);
33366
+ }));
33367
+ jsonOption(slideshows.command("export").description("Retired: PNG packs come from Studio Download").requiredOption("--id <id>").requiredOption("--output <path>")).action(withClient(async () => {
33368
+ throw new Error(HEADLESS_RENDER_DISABLED);
33369
+ }));
32882
33370
  const renders = program.command("renders").description("Inspect final render jobs");
32883
33371
  jsonOption(renders.command("create-asset").requiredOption("--file <path>").requiredOption("--idempotency-key <key>")).action(withClient(async (client, opts) => {
32884
33372
  const fileName = String(opts.file);
@@ -32893,16 +33381,15 @@ function buildV1Cli() {
32893
33381
  idempotencyKey: String(opts.idempotencyKey)
32894
33382
  });
32895
33383
  }));
32896
- jsonOption(renders.command("create").requiredOption("--template-id <id>").requiredOption("--slots <json-or-file>").option("--format <format>", "Final output format", "png").requiredOption("--idempotency-key <key>")).action(withClient(async (client, opts) => client.createRenderJob({
32897
- templateId: String(opts.templateId),
32898
- slots: renderSlots(await readJsonArgument(String(opts.slots))),
32899
- outputFormats: [String(opts.format)],
32900
- idempotencyKey: String(opts.idempotencyKey)
32901
- })));
33384
+ jsonOption(renders.command("create").description("Retired: PNG packs come from Studio Download").requiredOption("--template-id <id>").requiredOption("--slots <json-or-file>").option("--format <format>", "Final output format", "png").requiredOption("--idempotency-key <key>")).action(withClient(async () => {
33385
+ throw new Error(HEADLESS_RENDER_DISABLED);
33386
+ }));
32902
33387
  jsonOption(renders.command("get").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.getRenderJob(String(opts.id))));
32903
33388
  jsonOption(renders.command("manifest").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.getRenderManifest(String(opts.id))));
32904
33389
  jsonOption(renders.command("wait").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.pollRenderJob(String(opts.id))));
32905
- jsonOption(renders.command("export").requiredOption("--id <id>").requiredOption("--output <path>")).action(withClient(async (client, opts) => exportManifest(client, String(opts.id), String(opts.output))));
33390
+ jsonOption(renders.command("export").description("Retired: PNG packs come from Studio Download").requiredOption("--id <id>").requiredOption("--output <path>")).action(withClient(async () => {
33391
+ throw new Error(HEADLESS_RENDER_DISABLED);
33392
+ }));
32906
33393
  const mcp = program.command("mcp").description("Install and run the unified EasySociable MCP server");
32907
33394
  jsonOption(mcp.command("install").requiredOption("--host <host>", "codex, claude, hermes, or openclaw").option("--config-path <path>")).action((...args) => {
32908
33395
  const command = args.at(-1);