@easysociable/cli 0.1.3 → 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 (59) hide show
  1. package/README.md +13 -10
  2. package/content-engine/scripts/starter.mjs +3 -1
  3. package/content-engine/scripts/workbench.mjs +13 -8
  4. package/content-engine/starter/CLAUDE.md +217 -128
  5. package/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 +6 -3
  15. package/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/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/content-engine/starter/runs/_template/brief.md +1 -1
  25. package/content-engine/starter/runs/_template/pack.md +15 -7
  26. package/content-engine/starter/runs/_template/packet.md +6 -0
  27. package/content-engine/starter/swipe/_template.md +3 -1
  28. package/content-engine/starter/topics/README.md +4 -8
  29. package/content-engine/starter/topics/_index.md +12 -5
  30. package/content-engine/starter/topics/_template-item.md +64 -51
  31. package/content-engine/starter/topics/scoring.md +55 -4
  32. package/content-engine/workbench/__pycache__/build.cpython-310.pyc +0 -0
  33. package/content-engine/workbench/build.py +306 -97
  34. package/content-engine/workbench/serve.py +0 -0
  35. package/content-engine/workbench/template.html +173 -27
  36. package/dist/easysociable.js +191 -133
  37. package/dist/easysociable.js.map +1 -1
  38. package/package.json +13 -14
  39. package/skills/ONBOARDING.md +14 -15
  40. package/skills/easysociable-content/SKILL.md +100 -19
  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 -45
  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 +13 -16
  49. package/skills/easysociable-template/references/CATALOG_METADATA.md +21 -18
  50. package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
  51. package/skills/easysociable-template/workflows/FILL_AND_RENDER.md +4 -83
  52. package/skills/easysociable-template/workflows/OUT_OF_SCOPE.md +3 -2
  53. package/content-engine/starter/discovery/README.md +0 -18
  54. package/content-engine/starter/discovery/_index.md +0 -27
  55. package/content-engine/starter/discovery/_template.md +0 -41
  56. package/content-engine/starter/topics/WATCHLIST.md +0 -65
  57. package/content-engine/starter/topics/watchlists/README.md +0 -10
  58. package/content-engine/starter/topics/watchlists/_shared.md +0 -14
  59. package/content-engine/starter/topics/watchlists/default.md +0 -6
@@ -15351,6 +15351,10 @@ 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
  }
@@ -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({
@@ -16288,7 +16295,7 @@ function isRetryableStatus(status) {
16288
16295
  return status === 429 || status === 502 || status === 503 || status === 504;
16289
16296
  }
16290
16297
  function sleep(ms) {
16291
- return new Promise((resolve2) => setTimeout(resolve2, ms));
16298
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
16292
16299
  }
16293
16300
  var TemplateGatewayError, TemplateGatewayClient;
16294
16301
  var init_client = __esm({
@@ -16569,11 +16576,11 @@ var init_client = __esm({
16569
16576
  this.throwIfError(response.status, response.json);
16570
16577
  return parseSlideshowRun(response.json);
16571
16578
  }
16579
+ /** Permanently delete a Slideshow Run (ADR-0034 hard delete). */
16572
16580
  async trashSlideshowRun(runId) {
16573
16581
  const response = await this.request("DELETE", `/v1/slideshow/runs/${encodeURIComponent(runId)}`);
16574
16582
  this.throwIfError(response.status, response.json);
16575
- const rows = parseSlideshowRunSummaryList(response.json);
16576
- return rows[0];
16583
+ return parseSlideshowRunSummary(response.json);
16577
16584
  }
16578
16585
  async renderSlideshowRun(runId, body) {
16579
16586
  const input = renderSlideshowRunInputSchema.parse(body ?? {});
@@ -23452,7 +23459,7 @@ var init_protocol = __esm({
23452
23459
  return;
23453
23460
  }
23454
23461
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
23455
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
23462
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
23456
23463
  options?.signal?.throwIfAborted();
23457
23464
  }
23458
23465
  } catch (error52) {
@@ -23469,7 +23476,7 @@ var init_protocol = __esm({
23469
23476
  */
23470
23477
  request(request, resultSchema, options) {
23471
23478
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
23472
- return new Promise((resolve2, reject) => {
23479
+ return new Promise((resolve3, reject) => {
23473
23480
  const earlyReject = (error52) => {
23474
23481
  reject(error52);
23475
23482
  };
@@ -23547,7 +23554,7 @@ var init_protocol = __esm({
23547
23554
  if (!parseResult.success) {
23548
23555
  reject(parseResult.error);
23549
23556
  } else {
23550
- resolve2(parseResult.data);
23557
+ resolve3(parseResult.data);
23551
23558
  }
23552
23559
  } catch (error52) {
23553
23560
  reject(error52);
@@ -23808,12 +23815,12 @@ var init_protocol = __esm({
23808
23815
  }
23809
23816
  } catch {
23810
23817
  }
23811
- return new Promise((resolve2, reject) => {
23818
+ return new Promise((resolve3, reject) => {
23812
23819
  if (signal.aborted) {
23813
23820
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
23814
23821
  return;
23815
23822
  }
23816
- const timeoutId = setTimeout(resolve2, interval);
23823
+ const timeoutId = setTimeout(resolve3, interval);
23817
23824
  signal.addEventListener("abort", () => {
23818
23825
  clearTimeout(timeoutId);
23819
23826
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -26840,7 +26847,7 @@ var require_compile = __commonJS({
26840
26847
  const schOrFunc = root.refs[ref];
26841
26848
  if (schOrFunc)
26842
26849
  return schOrFunc;
26843
- let _sch = resolve2.call(this, root, ref);
26850
+ let _sch = resolve3.call(this, root, ref);
26844
26851
  if (_sch === void 0) {
26845
26852
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
26846
26853
  const { schemaId } = this.opts;
@@ -26867,7 +26874,7 @@ var require_compile = __commonJS({
26867
26874
  function sameSchemaEnv(s1, s2) {
26868
26875
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
26869
26876
  }
26870
- function resolve2(root, ref) {
26877
+ function resolve3(root, ref) {
26871
26878
  let sch;
26872
26879
  while (typeof (sch = this.refs[ref]) == "string")
26873
26880
  ref = sch;
@@ -27442,7 +27449,7 @@ var require_fast_uri = __commonJS({
27442
27449
  }
27443
27450
  return uri;
27444
27451
  }
27445
- function resolve2(baseURI, relativeURI, options) {
27452
+ function resolve3(baseURI, relativeURI, options) {
27446
27453
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
27447
27454
  const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
27448
27455
  schemelessOptions.skipEscape = true;
@@ -27669,7 +27676,7 @@ var require_fast_uri = __commonJS({
27669
27676
  var fastUri = {
27670
27677
  SCHEMES,
27671
27678
  normalize,
27672
- resolve: resolve2,
27679
+ resolve: resolve3,
27673
27680
  resolveComponent,
27674
27681
  equal,
27675
27682
  serialize,
@@ -31568,12 +31575,12 @@ var init_stdio2 = __esm({
31568
31575
  this.onclose?.();
31569
31576
  }
31570
31577
  send(message) {
31571
- return new Promise((resolve2) => {
31578
+ return new Promise((resolve3) => {
31572
31579
  const json2 = serializeMessage(message);
31573
31580
  if (this._stdout.write(json2)) {
31574
- resolve2();
31581
+ resolve3();
31575
31582
  } else {
31576
- this._stdout.once("drain", resolve2);
31583
+ this._stdout.once("drain", resolve3);
31577
31584
  }
31578
31585
  });
31579
31586
  }
@@ -31644,8 +31651,8 @@ async function serveStdioMcp(options) {
31644
31651
  await waitForStdioServerClose(server, process.stdin);
31645
31652
  }
31646
31653
  async function waitForStdioServerClose(server, stdin = process.stdin) {
31647
- await new Promise((resolve2) => {
31648
- server.onclose = () => resolve2();
31654
+ await new Promise((resolve3) => {
31655
+ server.onclose = () => resolve3();
31649
31656
  if (stdin.readableEnded || stdin.destroyed) {
31650
31657
  void server.close().catch(() => void 0);
31651
31658
  return;
@@ -31974,7 +31981,7 @@ var init_src4 = __esm({
31974
31981
  {
31975
31982
  name: "create_render_job",
31976
31983
  title: "Create render job",
31977
- 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).",
31978
31985
  effect: "local-write-job",
31979
31986
  inputSchema: {
31980
31987
  type: "object",
@@ -32275,8 +32282,8 @@ var init_src5 = __esm({
32275
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 },
32276
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 },
32277
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 },
32278
- { 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 },
32279
- { 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 }
32280
32287
  ];
32281
32288
  slideshowResourceTemplates = [
32282
32289
  { uriTemplate: "slideshow://runs/{runId}", name: "Slideshow Run", description: "Owner-scoped Run JSON snapshot", mimeType: "application/json" }
@@ -32311,7 +32318,7 @@ var init_src5 = __esm({
32311
32318
  case "trash_run":
32312
32319
  return { ok: true, data: await this.client.trashSlideshowRun(String(args.runId)), meta: m };
32313
32320
  case "render_run":
32314
- 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 };
32315
32322
  default:
32316
32323
  return { ok: false, error: { code: "UNKNOWN_TOOL", message: `Unknown tool: ${toolName}`, retryable: false }, meta: m };
32317
32324
  }
@@ -32343,13 +32350,20 @@ async function serveUnifiedStdioMcp(client) {
32343
32350
  resourceTemplates: unifiedResourceTemplates
32344
32351
  }, { backendName: "gateway" });
32345
32352
  }
32346
- 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;
32347
32354
  var init_src6 = __esm({
32348
32355
  "../../packages/mcp-server/src/index.ts"() {
32349
32356
  "use strict";
32350
32357
  init_src3();
32351
32358
  init_src4();
32352
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"]);
32353
32367
  removedTools = /* @__PURE__ */ new Set(["publish_run", "unpublish_run"]);
32354
32368
  templateNames = {
32355
32369
  create_draft: "create_template_draft",
@@ -32403,6 +32417,13 @@ var init_src6 = __esm({
32403
32417
  this.slideshow = new SlideshowMcpBackend(client);
32404
32418
  }
32405
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
+ }
32406
32427
  const templateTool = templateTools.find((tool) => tool.name === toolName);
32407
32428
  if (templateTool) return this.template.invoke(Object.entries(templateNames).find(([, name]) => name === toolName)?.[0] ?? toolName, args);
32408
32429
  const slideshowTool = slideshowTools.find((tool) => tool.name === toolName);
@@ -32486,11 +32507,10 @@ var init_v1_mcp = __esm({
32486
32507
 
32487
32508
  // src/v1-cli.ts
32488
32509
  init_src2();
32489
- import { cp, mkdtemp, readFile, rename as rename2, rm, writeFile, mkdir, chmod, copyFile } from "fs/promises";
32510
+ import { cp, readFile, writeFile, mkdir, chmod, copyFile } from "fs/promises";
32490
32511
  import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
32491
- import { createHash } from "crypto";
32492
- import { homedir as homedir3, tmpdir } from "os";
32493
- import { join as join4 } from "path";
32512
+ import { homedir as homedir3 } from "os";
32513
+ import { join as join4, resolve as resolve2 } from "path";
32494
32514
  import { Command } from "commander";
32495
32515
 
32496
32516
  // src/runtime/mcp-hosts.ts
@@ -32688,7 +32708,9 @@ function engineJson() {
32688
32708
  runs: "runs",
32689
32709
  exports: "exports",
32690
32710
  topics: "topics",
32691
- discovery: "discovery",
32711
+ needs: "needs",
32712
+ products: "products",
32713
+ recommendations: "recommendations",
32692
32714
  swipe: "swipe",
32693
32715
  profiles: "profiles",
32694
32716
  inbox: "inbox",
@@ -32729,7 +32751,95 @@ function inventory(engine, moduleUrl) {
32729
32751
  starterComplete: missing.length === 0
32730
32752
  };
32731
32753
  }
32732
- function validateEngine(rootOrEngine, moduleUrl, { strict = false } = {}) {
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 } = {}) {
32733
32843
  const engine = resolveEngine(rootOrEngine);
32734
32844
  const parent = resolve(engine, "..");
32735
32845
  const errors = [];
@@ -32746,28 +32856,37 @@ function validateEngine(rootOrEngine, moduleUrl, { strict = false } = {}) {
32746
32856
  else if (result.missing.length > 0)
32747
32857
  errors.push(`Starter incomplete: ${result.missing.length} file(s) missing (use engine init --yes to add only missing files)`);
32748
32858
  }
32749
- return { ...result, valid: errors.length === 0, errors, created: [] };
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 } : {} };
32750
32863
  }
32751
32864
  function python3Bin() {
32752
32865
  const result = spawnSync("python3", ["--version"], { encoding: "utf8" });
32753
32866
  return result.status === 0 ? "python3" : "";
32754
32867
  }
32755
- function ensureWorkbench(engine, moduleUrl) {
32868
+ function ensureWorkbench(engine, moduleUrl, { update = false } = {}) {
32756
32869
  const destDir = join3(engine, "workbench");
32757
32870
  mkdirSync2(destDir, { recursive: true });
32758
32871
  const copied = [];
32872
+ const updated = [];
32759
32873
  const skipped = [];
32760
32874
  const src = workbenchSrc(moduleUrl);
32761
32875
  for (const name of WORKBENCH_FILES) {
32762
32876
  const dest = join3(destDir, name);
32763
- if (existsSync3(dest)) {
32877
+ const from = join3(src, name);
32878
+ if (existsSync3(dest) && !update) {
32764
32879
  skipped.push(name);
32765
32880
  continue;
32766
32881
  }
32767
- copyFileSync(join3(src, name), dest);
32768
- copied.push(name);
32882
+ const existed = existsSync3(dest);
32883
+ copyFileSync(from, dest);
32884
+ if (existed)
32885
+ updated.push(name);
32886
+ else
32887
+ copied.push(name);
32769
32888
  }
32770
- return { destDir, copied, skipped };
32889
+ return { destDir, copied, updated, skipped };
32771
32890
  }
32772
32891
  function buildWorkbench(engine) {
32773
32892
  const python = python3Bin();
@@ -32799,7 +32918,7 @@ async function startWorkbench(engine, moduleUrl, { noOpen = false } = {}) {
32799
32918
  const python = python3Bin();
32800
32919
  if (!python)
32801
32920
  return { ok: false, code: "PYTHON3_REQUIRED", message: "python3 is required for the workbench. Install Python 3 and retry." };
32802
- ensureWorkbench(engine, moduleUrl);
32921
+ ensureWorkbench(engine, moduleUrl, { update: true });
32803
32922
  const built = buildWorkbench(engine);
32804
32923
  if (!built.ok)
32805
32924
  return built;
@@ -32931,6 +33050,7 @@ function readCliVersion() {
32931
33050
  return "0.0.0";
32932
33051
  }
32933
33052
  }
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.";
32934
33054
  var credentialPath = join4(homedir3(), ".config", "easysociable", "credentials.json");
32935
33055
  function resolveInstallSkillIds(options) {
32936
33056
  return parseInstallSkills(typeof options.skills === "string" ? options.skills : void 0);
@@ -33050,29 +33170,20 @@ async function applyInstallation(options) {
33050
33170
  for (const skill of resolveInstallSkillIds(options))
33051
33171
  await cp(join4(String(plan.source), skill), join4(host.skillsPath, skill), { recursive: true, force: false, errorOnExist: false });
33052
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
+ `);
33178
+ }
33053
33179
  return { ...plan, applied: true, backups };
33054
33180
  }
33055
- async function readJsonArgument(value) {
33056
- if (existsSync4(value))
33057
- return JSON.parse(await readFile(value, "utf8"));
33058
- return JSON.parse(value);
33059
- }
33060
33181
  function mimeTypeFor(fileName) {
33061
33182
  const extension = fileName.toLowerCase().split(".").pop();
33062
33183
  if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
33063
33184
  if (extension === "webp") return "image/webp";
33064
33185
  return "image/png";
33065
33186
  }
33066
- function renderSlots(value) {
33067
- if (!value || typeof value !== "object" || Array.isArray(value))
33068
- throw new Error("Render slots must be a JSON object");
33069
- const entries = Object.entries(value);
33070
- for (const [key, item] of entries) {
33071
- if (!(typeof item === "string" || typeof item === "number" || item === null))
33072
- throw new Error(`Render slot ${key} must be a string, number or null`);
33073
- }
33074
- return Object.fromEntries(entries);
33075
- }
33076
33187
  function withClient(action) {
33077
33188
  return async (...args) => {
33078
33189
  const command = args.at(-1);
@@ -33087,69 +33198,6 @@ function withClient(action) {
33087
33198
  }
33088
33199
  };
33089
33200
  }
33090
- async function exportManifest(client, jobId, output) {
33091
- const job = await client.getRenderJob(jobId);
33092
- if (job.status !== "completed" || !job.manifest)
33093
- throw new Error(`Render job ${jobId} is not completed or has no manifest`);
33094
- const staging = await mkdtemp(join4(tmpdir(), "easysociable-export-"));
33095
- try {
33096
- for (const [index, artifact] of job.manifest.artifacts.entries()) {
33097
- const response = await fetch(artifact.url);
33098
- if (!response.ok) throw new Error(`Artifact download failed (${response.status})`);
33099
- const bytes = Buffer.from(await response.arrayBuffer());
33100
- const checksum = createHash("sha256").update(bytes).digest("hex");
33101
- if (checksum !== artifact.sha256) throw new Error(`Artifact checksum mismatch for page ${index + 1}`);
33102
- await writeFile(join4(staging, `${String(index + 1).padStart(2, "0")}.png`), bytes);
33103
- }
33104
- await writeFile(join4(staging, "manifest.json"), `${JSON.stringify(job.manifest, null, 2)}
33105
- `);
33106
- await rm(output, { recursive: true, force: true });
33107
- await rename2(staging, output);
33108
- return { jobId, output, pages: job.manifest.artifacts.length, manifest: job.manifest };
33109
- } catch (error52) {
33110
- await rm(staging, { recursive: true, force: true });
33111
- throw error52;
33112
- }
33113
- }
33114
- async function exportSlideshowRun(client, runId, output) {
33115
- const manifest = await client.getSlideshowRunManifest(runId);
33116
- const staging = await mkdtemp(join4(tmpdir(), "easysociable-slideshow-export-"));
33117
- try {
33118
- const pages = [];
33119
- for (const artifact of manifest.artifacts) {
33120
- const bytes = Buffer.from(await client.downloadSlideshowArtifact(runId, artifact.pageIndex));
33121
- const checksum = createHash("sha256").update(bytes).digest("hex");
33122
- if (checksum !== artifact.sha256)
33123
- throw new Error(`Artifact checksum mismatch for page ${artifact.pageIndex + 1}`);
33124
- const role = artifact.role || `page${artifact.pageIndex}`;
33125
- const fileName = `${String(artifact.pageIndex + 1).padStart(2, "0")}-${role}.png`;
33126
- await writeFile(join4(staging, fileName), bytes);
33127
- pages.push({
33128
- pageIndex: artifact.pageIndex,
33129
- role,
33130
- file: fileName,
33131
- bytes: bytes.length,
33132
- sha256: checksum,
33133
- width: artifact.width,
33134
- height: artifact.height
33135
- });
33136
- }
33137
- const localManifest = {
33138
- schema: "slideshow-hosted-export.v1",
33139
- runId: manifest.runId,
33140
- deckJobId: manifest.deckJobId,
33141
- pages
33142
- };
33143
- await writeFile(join4(staging, "manifest.json"), `${JSON.stringify(localManifest, null, 2)}
33144
- `);
33145
- await rm(output, { recursive: true, force: true });
33146
- await rename2(staging, output);
33147
- return { runId, output, pages: pages.length, manifest: localManifest };
33148
- } catch (error52) {
33149
- await rm(staging, { recursive: true, force: true });
33150
- throw error52;
33151
- }
33152
- }
33153
33201
  function addCrudCommands(parent, resource, routes) {
33154
33202
  jsonOption(parent.command("list").description(`List ${resource}`)).action(withClient(async (client) => client.requestJson("GET", routes.list)));
33155
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)))));
@@ -33195,10 +33243,18 @@ function buildV1Cli() {
33195
33243
  throw new Error(errors?.[0] ?? result.code ?? "engine init failed");
33196
33244
  }
33197
33245
  });
33198
- 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")).action((...args) => {
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) => {
33199
33247
  const command = args.at(-1);
33200
33248
  const opts = command.opts();
33201
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
+ }
33202
33258
  const result = validateRun(CLI_MODULE_URL, {
33203
33259
  run: String(opts.run),
33204
33260
  requirePack: opts.requirePack === true,
@@ -33215,31 +33271,30 @@ function buildV1Cli() {
33215
33271
  }
33216
33272
  if (!opts.root)
33217
33273
  throw new Error("Pass --root <path> or --run <path>.");
33218
- const report = validateEngine(String(opts.root), CLI_MODULE_URL, { strict: true });
33274
+ const report = validateEngine(String(opts.root), CLI_MODULE_URL, {
33275
+ strict: true,
33276
+ requireKnowledge: opts.requireKnowledge === true
33277
+ });
33219
33278
  print({ ...report, action: "validated" }, commandWantsJson(command));
33220
33279
  if (!report.valid)
33221
33280
  throw new Error(report.errors[0] ?? "engine validate failed");
33222
33281
  });
33223
- jsonOption(engine.command("serve").description("Build and start the workbench on :8765").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot()).option("--no-open", "Do not open a browser")).action(async (...args) => {
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) => {
33224
33283
  const command = args.at(-1);
33225
33284
  const opts = command.opts();
33226
33285
  const enginePath = resolveEngine(String(opts.engine));
33227
- ensureWorkbench(enginePath, CLI_MODULE_URL);
33228
- const built = buildWorkbench(enginePath);
33229
- if (!built.ok)
33230
- throw new Error(built.message);
33231
33286
  const started = await startWorkbench(enginePath, CLI_MODULE_URL, { noOpen: opts.noOpen === true });
33232
33287
  print({ action: "serve", engine: enginePath, ...started }, commandWantsJson(command));
33233
33288
  if (!started.ok)
33234
33289
  throw new Error("message" in started ? String(started.message) : "engine serve failed");
33235
33290
  });
33236
- jsonOption(engine.command("build").description("Rebuild workbench/index.html from markdown indexes").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot())).action((...args) => {
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) => {
33237
33292
  const command = args.at(-1);
33238
33293
  const opts = command.opts();
33239
33294
  const enginePath = resolveEngine(String(opts.engine));
33240
- ensureWorkbench(enginePath, CLI_MODULE_URL);
33295
+ const workbench = ensureWorkbench(enginePath, CLI_MODULE_URL, { update: true });
33241
33296
  const built = buildWorkbench(enginePath);
33242
- print({ action: "build", engine: enginePath, ...built }, commandWantsJson(command));
33297
+ print({ action: "build", engine: enginePath, workbench, ...built }, commandWantsJson(command));
33243
33298
  if (!built.ok)
33244
33299
  throw new Error(built.message);
33245
33300
  });
@@ -33306,8 +33361,12 @@ function buildV1Cli() {
33306
33361
  jsonOption(slideshows.command("archive").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.archiveSlideshowRun(String(opts.id))));
33307
33362
  jsonOption(slideshows.command("restore").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.restoreSlideshowRun(String(opts.id))));
33308
33363
  jsonOption(slideshows.command("delete").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.trashSlideshowRun(String(opts.id))));
33309
- 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) })));
33310
- 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
+ }));
33311
33370
  const renders = program.command("renders").description("Inspect final render jobs");
33312
33371
  jsonOption(renders.command("create-asset").requiredOption("--file <path>").requiredOption("--idempotency-key <key>")).action(withClient(async (client, opts) => {
33313
33372
  const fileName = String(opts.file);
@@ -33322,16 +33381,15 @@ function buildV1Cli() {
33322
33381
  idempotencyKey: String(opts.idempotencyKey)
33323
33382
  });
33324
33383
  }));
33325
- 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({
33326
- templateId: String(opts.templateId),
33327
- slots: renderSlots(await readJsonArgument(String(opts.slots))),
33328
- outputFormats: [String(opts.format)],
33329
- idempotencyKey: String(opts.idempotencyKey)
33330
- })));
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
+ }));
33331
33387
  jsonOption(renders.command("get").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.getRenderJob(String(opts.id))));
33332
33388
  jsonOption(renders.command("manifest").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.getRenderManifest(String(opts.id))));
33333
33389
  jsonOption(renders.command("wait").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.pollRenderJob(String(opts.id))));
33334
- 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
+ }));
33335
33393
  const mcp = program.command("mcp").description("Install and run the unified EasySociable MCP server");
33336
33394
  jsonOption(mcp.command("install").requiredOption("--host <host>", "codex, claude, hermes, or openclaw").option("--config-path <path>")).action((...args) => {
33337
33395
  const command = args.at(-1);