@easysociable/cli 0.1.8 → 0.1.10

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 (93) hide show
  1. package/README.md +30 -9
  2. package/dist/easysociable.js +234 -98
  3. package/dist/easysociable.js.map +1 -1
  4. package/package.json +2 -2
  5. package/scripts/engine/migrate-split-vault.py +75 -0
  6. package/skills/ONBOARDING.md +19 -4
  7. package/skills/easysociable-content/SKILL.md +25 -9
  8. package/content-engine/scripts/fixture.test.mjs +0 -24
  9. package/content-engine/scripts/starter.mjs +0 -58
  10. package/content-engine/scripts/validate.test.mjs +0 -101
  11. package/content-engine/scripts/workbench.mjs +0 -140
  12. package/content-engine/starter/CLAUDE.md +0 -908
  13. package/content-engine/starter/accounts/README.md +0 -9
  14. package/content-engine/starter/accounts/_index.md +0 -31
  15. package/content-engine/starter/accounts/overrides/README.md +0 -3
  16. package/content-engine/starter/engine.json +0 -23
  17. package/content-engine/starter/exports/README.md +0 -28
  18. package/content-engine/starter/hits/README.md +0 -5
  19. package/content-engine/starter/hits/_index.md +0 -30
  20. package/content-engine/starter/hits/_template.md +0 -46
  21. package/content-engine/starter/hits/entries/README.md +0 -3
  22. package/content-engine/starter/inbox/README.md +0 -9
  23. package/content-engine/starter/inbox/_index.md +0 -24
  24. package/content-engine/starter/inbox/entries/README.md +0 -3
  25. package/content-engine/starter/needs/README.md +0 -38
  26. package/content-engine/starter/needs/_index.md +0 -31
  27. package/content-engine/starter/needs/_template.md +0 -24
  28. package/content-engine/starter/needs/entries/README.md +0 -5
  29. package/content-engine/starter/platforms/README.md +0 -19
  30. package/content-engine/starter/platforms/ig.md +0 -69
  31. package/content-engine/starter/platforms/instagram.md +0 -5
  32. package/content-engine/starter/platforms/linkedin.md +0 -72
  33. package/content-engine/starter/platforms/threads.md +0 -69
  34. package/content-engine/starter/platforms/tiktok.md +0 -71
  35. package/content-engine/starter/platforms/x.md +0 -78
  36. package/content-engine/starter/platforms/xiaohongshu.md +0 -19
  37. package/content-engine/starter/products/README.md +0 -38
  38. package/content-engine/starter/products/_index.md +0 -25
  39. package/content-engine/starter/products/_template.md +0 -56
  40. package/content-engine/starter/products/entries/P-001-easysociable.md +0 -150
  41. package/content-engine/starter/products/entries/P-002-content-engine.md +0 -136
  42. package/content-engine/starter/profiles/_index.md +0 -16
  43. package/content-engine/starter/profiles/default/README.md +0 -17
  44. package/content-engine/starter/profiles/default/audience.md +0 -38
  45. package/content-engine/starter/profiles/default/boundaries.md +0 -14
  46. package/content-engine/starter/profiles/default/expression.md +0 -18
  47. package/content-engine/starter/profiles/default/handles.md +0 -8
  48. package/content-engine/starter/profiles/default/keywords.md +0 -31
  49. package/content-engine/starter/profiles/default/lanes.md +0 -12
  50. package/content-engine/starter/profiles/default/pillars.md +0 -33
  51. package/content-engine/starter/profiles/default/stories.md +0 -15
  52. package/content-engine/starter/profiles/default/voice.md +0 -38
  53. package/content-engine/starter/published/_index.md +0 -20
  54. package/content-engine/starter/raw/README.md +0 -4
  55. package/content-engine/starter/raw/_index.md +0 -12
  56. package/content-engine/starter/recommendations/README.md +0 -21
  57. package/content-engine/starter/recommendations/_index.md +0 -21
  58. package/content-engine/starter/recommendations/_template.md +0 -44
  59. package/content-engine/starter/runs/README.md +0 -22
  60. package/content-engine/starter/runs/_index.md +0 -41
  61. package/content-engine/starter/runs/_template/brief.md +0 -76
  62. package/content-engine/starter/runs/_template/draft.md +0 -11
  63. package/content-engine/starter/runs/_template/editor.md +0 -18
  64. package/content-engine/starter/runs/_template/feedback.md +0 -15
  65. package/content-engine/starter/runs/_template/idea.md +0 -27
  66. package/content-engine/starter/runs/_template/pack.md +0 -55
  67. package/content-engine/starter/runs/_template/packet.md +0 -31
  68. package/content-engine/starter/runs/_template/rubric.md +0 -19
  69. package/content-engine/starter/swipe/_index.md +0 -27
  70. package/content-engine/starter/swipe/_template.md +0 -36
  71. package/content-engine/starter/topics/README.md +0 -21
  72. package/content-engine/starter/topics/_index.md +0 -51
  73. package/content-engine/starter/topics/_template-item.md +0 -106
  74. package/content-engine/starter/topics/items/README.md +0 -4
  75. package/content-engine/starter/topics/scoring.md +0 -255
  76. package/content-engine/starter/wiki/README.md +0 -13
  77. package/content-engine/starter/wiki/atoms/_index.md +0 -23
  78. package/content-engine/starter/wiki/atoms/_template.md +0 -31
  79. package/content-engine/starter/wiki/claims/_index.md +0 -26
  80. package/content-engine/starter/wiki/claims/_template.md +0 -38
  81. package/content-engine/starter/workbench/README.md +0 -17
  82. package/content-engine/workbench/__pycache__/build.cpython-310.pyc +0 -0
  83. package/content-engine/workbench/build.py +0 -1467
  84. package/content-engine/workbench/serve.py +0 -53
  85. package/content-engine/workbench/template.html +0 -2683
  86. /package/{content-engine/scripts → scripts/engine}/contracts.mjs +0 -0
  87. /package/{content-engine/scripts → scripts/engine}/export-contracts.mjs +0 -0
  88. /package/{content-engine → scripts/engine}/fixtures/golden-run-en/draft.md +0 -0
  89. /package/{content-engine → scripts/engine}/fixtures/golden-run-en/idea.md +0 -0
  90. /package/{content-engine → scripts/engine}/fixtures/golden-run-en/pack-tiktok.json +0 -0
  91. /package/{content-engine/scripts → scripts/engine}/validate-export.mjs +0 -0
  92. /package/{content-engine/scripts → scripts/engine}/validate-pack.mjs +0 -0
  93. /package/{content-engine/scripts → scripts/engine}/validate-run.mjs +0 -0
package/README.md CHANGED
@@ -21,27 +21,48 @@ Default install does **not** include `easysociable-content`. Add it when you wan
21
21
 
22
22
  ```bash
23
23
  easysociable install --skills content,template,slideshow --yes
24
- easysociable engine init --root /workspace --yes
25
- easysociable engine serve --engine /workspace/Content Engine --no-open
26
24
  ```
27
25
 
28
- Then tell the Agent to follow `Content Engine/CLAUDE.md`. The starter includes
29
- separate `products/` (owned offers) and `recommendations/` (third-party review
30
- subjects) libraries; neither is a substitute for `needs/`.
26
+ **Preferred:** clone the public core:
27
+
28
+ ```bash
29
+ git clone https://github.com/murphywuwu/content-engine.git
30
+ cd content-engine
31
+ python3 workbench/build.py
32
+ python3 workbench/serve.py --no-open
33
+ ```
34
+
35
+ See https://github.com/murphywuwu/content-engine (LAYOUT.md, migrate-split-vault.py).
36
+
37
+ **Optional:** `easysociable engine init --root … --yes` clones that repo into
38
+ `Content Engine/` (never overwrites an existing engine).
39
+
40
+ Then tell the Agent to follow `CLAUDE.md`. The core includes separate
41
+ `products/` (owned offers) and `recommendations/` (third-party review subjects);
42
+ neither is a substitute for `needs/`.
31
43
 
32
44
  Lazy-auth guidance lives in the package at `skills/ONBOARDING.md`.
33
45
 
34
46
  ## Content Engine
35
47
 
48
+ Core methodology lives in the **public GitHub repo**, not inside this CLI
49
+ tarball. `engine serve` / `build` do **not** overwrite local workbench files
50
+ unless you pass `--sync-workbench` (pulls from the public core checkout /
51
+ `CONTENT_ENGINE_CORE`).
52
+
36
53
  | Command | Purpose |
37
54
  |---------|---------|
38
- | `easysociable engine init [--root] [--yes] [--serve]` | Scaffold starter (never overwrites) |
39
- | `easysociable engine serve [--engine] [--no-open]` | Sync workbench runtime from CLI, rebuild, start on `:8765` |
40
- | `easysociable engine build [--engine]` | Sync workbench runtime from CLI, then rebuild `workbench/index.html` |
55
+ | `easysociable engine init [--root] [--yes] [--serve]` | Clone public core into `Content Engine/` (no overwrite) |
56
+ | `easysociable engine update [--engine] [--yes]` | Sync core rules from the public template; preview first, apply with `--yes`. Never touches vault data |
57
+ | `easysociable engine serve [--engine] [--no-open] [--sync-workbench]` | Rebuild + start on `:8765` |
58
+ | `easysociable engine build [--engine] [--sync-workbench]` | Rebuild `workbench/index.html` |
41
59
  | `easysociable engine detect` | Find engines under search roots |
42
- | `easysociable engine validate --root …` | Starter completeness |
60
+ | `easysociable engine validate --root …` | Core marker completeness |
43
61
  | `easysociable engine validate --run …` | Run / pack checks before render |
44
62
 
63
+ Optional `engine.json` field `vault_root` (default `"."`) points data at a
64
+ separate vault; see the public LAYOUT.md.
65
+
45
66
  The Workbench exposes Products and Recommendations as separate read-only
46
67
  indexes. Topics can cite either one without forcing a product connection.
47
68
 
@@ -32639,10 +32639,12 @@ function resolveDefaultSkillsSource(moduleUrl) {
32639
32639
  import { spawn, spawnSync } from "child_process";
32640
32640
  import {
32641
32641
  copyFileSync,
32642
+ cpSync,
32642
32643
  existsSync as existsSync3,
32643
32644
  mkdirSync as mkdirSync2,
32644
32645
  readdirSync,
32645
32646
  readFileSync as readFileSync2,
32647
+ rmSync,
32646
32648
  statSync,
32647
32649
  writeFileSync as writeFileSync2
32648
32650
  } from "fs";
@@ -32652,29 +32654,61 @@ import { basename, dirname as dirname3, join as join3, resolve } from "path";
32652
32654
  var ENGINE_ROOT_NAME = "Content Engine";
32653
32655
  var WORKBENCH_PORT = 8765;
32654
32656
  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
- }
32657
+ var CONTENT_ENGINE_REPO = "https://github.com/murphywuwu/content-engine.git";
32658
+ var WORKBENCH_FILES = ["template.html", "build.py", "serve.py", "roots.py"];
32659
+ var REQUIRED_RELATIVE = [
32660
+ "CLAUDE.md",
32661
+ "engine.json",
32662
+ "LAYOUT.md",
32663
+ "workbench/build.py",
32664
+ "workbench/serve.py",
32665
+ "workbench/template.html",
32666
+ "workbench/roots.py",
32667
+ "needs/_index.md",
32668
+ "topics/_index.md",
32669
+ "runs/_index.md",
32670
+ "profiles/_index.md"
32671
+ ];
32675
32672
  function isDir(path) {
32676
32673
  return existsSync3(path) && statSync(path).isDirectory();
32677
32674
  }
32675
+ function scriptsDir(moduleUrl) {
32676
+ return join3(cliPackageRoot(moduleUrl), "scripts", "engine");
32677
+ }
32678
+ function contentEngineRepoUrl() {
32679
+ return (process.env.CONTENT_ENGINE_REPO || CONTENT_ENGINE_REPO).trim() || CONTENT_ENGINE_REPO;
32680
+ }
32681
+ function ensureCoreCheckout({ update = false } = {}) {
32682
+ const local = (process.env.CONTENT_ENGINE_CORE || "").trim();
32683
+ if (local) {
32684
+ const abs = resolve(local);
32685
+ if (!existsSync3(join3(abs, "CLAUDE.md")))
32686
+ throw new Error(`CONTENT_ENGINE_CORE=${abs} is missing CLAUDE.md`);
32687
+ return abs;
32688
+ }
32689
+ const cache = join3(homedir2(), ".cache", "easysociable", "content-engine");
32690
+ const marker = join3(cache, "CLAUDE.md");
32691
+ if (existsSync3(marker) && !update)
32692
+ return cache;
32693
+ mkdirSync2(dirname3(cache), { recursive: true });
32694
+ if (existsSync3(marker) && update) {
32695
+ const pull = spawnSync("git", ["-C", cache, "pull", "--ff-only"], { encoding: "utf8" });
32696
+ if (pull.status !== 0) {
32697
+ rmSync(cache, { recursive: true, force: true });
32698
+ } else {
32699
+ return cache;
32700
+ }
32701
+ }
32702
+ if (existsSync3(cache))
32703
+ rmSync(cache, { recursive: true, force: true });
32704
+ const clone3 = spawnSync("git", ["clone", "--depth", "1", contentEngineRepoUrl(), cache], { encoding: "utf8" });
32705
+ if (clone3.status !== 0 || !existsSync3(marker)) {
32706
+ throw new Error(
32707
+ `Failed to clone Content Engine core from ${contentEngineRepoUrl()}. ${clone3.stderr || clone3.stdout || ""}`.trim()
32708
+ );
32709
+ }
32710
+ return cache;
32711
+ }
32678
32712
  function isEngine(path) {
32679
32713
  return isDir(path) && existsSync3(join3(path, "CLAUDE.md")) && isDir(join3(path, "runs"));
32680
32714
  }
@@ -32684,62 +32718,10 @@ function resolveEngine(rootOrEngine) {
32684
32718
  return absolute;
32685
32719
  return join3(absolute, ENGINE_ROOT_NAME);
32686
32720
  }
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
- hits: "hits",
32713
- products: "products",
32714
- recommendations: "recommendations",
32715
- swipe: "swipe",
32716
- profiles: "profiles",
32717
- inbox: "inbox",
32718
- accounts: "accounts",
32719
- raw: "raw",
32720
- published: "published",
32721
- wiki: "wiki",
32722
- platforms: "platforms",
32723
- workbench: "workbench"
32724
- }
32725
- }, null, 2)}
32726
- `;
32727
- }
32728
- function starterFiles(moduleUrl) {
32729
- const root = starterDir(moduleUrl);
32730
- const files = {};
32731
- for (const path of walkFiles(root)) {
32732
- const relativePath = path.slice(root.length + 1).split("\\").join("/");
32733
- files[relativePath] = readFileSync2(path, "utf8");
32734
- }
32735
- files["engine.json"] = engineJson();
32736
- return files;
32737
- }
32738
- function inventory(engine, moduleUrl) {
32739
- const files = starterFiles(moduleUrl);
32721
+ function inventory(engine, _moduleUrl) {
32740
32722
  const missing = [];
32741
32723
  const existing = [];
32742
- for (const relative of Object.keys(files)) {
32724
+ for (const relative of REQUIRED_RELATIVE) {
32743
32725
  const path = join3(engine, relative);
32744
32726
  (existsSync3(path) ? existing : missing).push(relative);
32745
32727
  }
@@ -32747,6 +32729,7 @@ function inventory(engine, moduleUrl) {
32747
32729
  engine,
32748
32730
  engineExists: isDir(engine),
32749
32731
  rootName: ENGINE_ROOT_NAME,
32732
+ repo: contentEngineRepoUrl(),
32750
32733
  missing,
32751
32734
  existing,
32752
32735
  starterComplete: missing.length === 0
@@ -32855,7 +32838,7 @@ function validateEngine(rootOrEngine, moduleUrl, { strict = false, requireKnowle
32855
32838
  if (!result.engineExists)
32856
32839
  errors.push("Content Engine/ folder does not exist");
32857
32840
  else if (result.missing.length > 0)
32858
- errors.push(`Starter incomplete: ${result.missing.length} file(s) missing (use engine init --yes to add only missing files)`);
32841
+ errors.push(`Engine incomplete: ${result.missing.length} file(s) missing (clone ${contentEngineRepoUrl()} or run engine init --yes)`);
32859
32842
  }
32860
32843
  const knowledge = requireKnowledge ? checkKnowledge(engine) : void 0;
32861
32844
  if (knowledge && !knowledge.valid)
@@ -32866,20 +32849,31 @@ function python3Bin() {
32866
32849
  const result = spawnSync("python3", ["--version"], { encoding: "utf8" });
32867
32850
  return result.status === 0 ? "python3" : "";
32868
32851
  }
32869
- function ensureWorkbench(engine, moduleUrl, { update = false } = {}) {
32852
+ function wantWorkbenchSync(explicit) {
32853
+ if (explicit === true)
32854
+ return true;
32855
+ if (explicit === false)
32856
+ return false;
32857
+ const env = (process.env.EASYSOCIABLE_SYNC_WORKBENCH || "").trim().toLowerCase();
32858
+ return env === "1" || env === "true" || env === "yes";
32859
+ }
32860
+ function ensureWorkbench(engine, _moduleUrl, { update = false } = {}) {
32870
32861
  const destDir = join3(engine, "workbench");
32871
32862
  mkdirSync2(destDir, { recursive: true });
32872
32863
  const copied = [];
32873
32864
  const updated = [];
32874
32865
  const skipped = [];
32875
- const src = workbenchSrc(moduleUrl);
32866
+ const needFetch = update || WORKBENCH_FILES.some((name) => !existsSync3(join3(destDir, name)));
32867
+ const srcRoot = needFetch ? join3(ensureCoreCheckout({ update }), "workbench") : "";
32876
32868
  for (const name of WORKBENCH_FILES) {
32877
32869
  const dest = join3(destDir, name);
32878
- const from = join3(src, name);
32879
32870
  if (existsSync3(dest) && !update) {
32880
32871
  skipped.push(name);
32881
32872
  continue;
32882
32873
  }
32874
+ const from = join3(srcRoot, name);
32875
+ if (!existsSync3(from))
32876
+ throw new Error(`Missing ${name} in Content Engine core checkout`);
32883
32877
  const existed = existsSync3(dest);
32884
32878
  copyFileSync(from, dest);
32885
32879
  if (existed)
@@ -32895,7 +32889,7 @@ function buildWorkbench(engine) {
32895
32889
  return { ok: false, code: "PYTHON3_REQUIRED", message: "python3 is required for the workbench. Install Python 3 and retry." };
32896
32890
  const script = join3(engine, "workbench", "build.py");
32897
32891
  if (!existsSync3(script))
32898
- return { ok: false, code: "WORKBENCH_MISSING", message: "workbench/build.py is missing. Re-run easysociable engine init --yes." };
32892
+ return { ok: false, code: "WORKBENCH_MISSING", message: `workbench/build.py is missing. Clone ${contentEngineRepoUrl()} or run engine init --yes.` };
32899
32893
  const result = spawnSync(python, [script], { encoding: "utf8", cwd: engine });
32900
32894
  if (result.status !== 0)
32901
32895
  return { ok: false, code: "WORKBENCH_BUILD_FAILED", message: result.stderr.trim() || result.stdout.trim() || "workbench build failed" };
@@ -32915,11 +32909,15 @@ function openUrl(url2) {
32915
32909
  const args = process.platform === "win32" ? ["/c", "start", url2] : [url2];
32916
32910
  spawn(command, args, { stdio: "ignore", detached: true }).unref();
32917
32911
  }
32918
- async function startWorkbench(engine, moduleUrl, { noOpen = false } = {}) {
32912
+ async function startWorkbench(engine, moduleUrl, {
32913
+ noOpen = false,
32914
+ syncWorkbench = false
32915
+ } = {}) {
32919
32916
  const python = python3Bin();
32920
32917
  if (!python)
32921
32918
  return { ok: false, code: "PYTHON3_REQUIRED", message: "python3 is required for the workbench. Install Python 3 and retry." };
32922
- ensureWorkbench(engine, moduleUrl, { update: true });
32919
+ const sync = wantWorkbenchSync(syncWorkbench || void 0);
32920
+ ensureWorkbench(engine, moduleUrl, { update: sync });
32923
32921
  const built = buildWorkbench(engine);
32924
32922
  if (!built.ok)
32925
32923
  return built;
@@ -32947,7 +32945,7 @@ async function initEngine(rootOrEngine, moduleUrl, {
32947
32945
  return {
32948
32946
  ...preview,
32949
32947
  action: "preview",
32950
- next: "Review missing files and re-run with --yes"
32948
+ next: isEngine(engine) ? "Engine already present; nothing to do" : `Re-run with --yes to git clone ${contentEngineRepoUrl()} into this path (or clone manually)`
32951
32949
  };
32952
32950
  }
32953
32951
  const parent = resolve(engine, "..");
@@ -32961,15 +32959,24 @@ async function initEngine(rootOrEngine, moduleUrl, {
32961
32959
  created: []
32962
32960
  };
32963
32961
  }
32964
- mkdirSync2(engine, { recursive: true });
32965
- const report = inventory(engine, moduleUrl);
32966
32962
  const created = [];
32967
- const files = starterFiles(moduleUrl);
32968
- for (const relative of report.missing) {
32969
- const path = join3(engine, relative);
32970
- mkdirSync2(dirname3(path), { recursive: true });
32971
- writeFileSync2(path, files[relative], { encoding: "utf8", flag: "wx" });
32972
- created.push(relative);
32963
+ if (isEngine(engine)) {
32964
+ } else if (existsSync3(engine) && readdirSync(engine).filter((n) => n !== ".DS_Store").length > 0) {
32965
+ return {
32966
+ ...validateEngine(engine, moduleUrl),
32967
+ created,
32968
+ action: "applied",
32969
+ valid: false,
32970
+ code: "ENGINE_DIR_DIRTY",
32971
+ errors: [`${engine} exists but is not a Content Engine. Remove it or clone ${contentEngineRepoUrl()} into an empty path.`]
32972
+ };
32973
+ } else {
32974
+ const core = ensureCoreCheckout();
32975
+ if (existsSync3(engine))
32976
+ rmSync(engine, { recursive: true, force: true });
32977
+ cpSync(core, engine, { recursive: true });
32978
+ rmSync(join3(engine, ".git"), { recursive: true, force: true });
32979
+ created.push("CLAUDE.md", "engine.json", "workbench/", "(cloned public core)");
32973
32980
  }
32974
32981
  const workbench = ensureWorkbench(engine, moduleUrl);
32975
32982
  const python = python3Bin();
@@ -32981,7 +32988,7 @@ async function initEngine(rootOrEngine, moduleUrl, {
32981
32988
  action: "applied",
32982
32989
  valid: false,
32983
32990
  code: "PYTHON3_REQUIRED",
32984
- errors: ["python3 is required for the workbench. Install Python 3 and retry. Starter files were written; workbench was not started."]
32991
+ errors: ["python3 is required for the workbench. Install Python 3 and retry."]
32985
32992
  };
32986
32993
  }
32987
32994
  const built = buildWorkbench(engine);
@@ -32992,7 +32999,123 @@ async function initEngine(rootOrEngine, moduleUrl, {
32992
32999
  workbench,
32993
33000
  workbenchBuild: built,
32994
33001
  workbenchStart: started,
32995
- action: "applied"
33002
+ action: "applied",
33003
+ source: contentEngineRepoUrl()
33004
+ };
33005
+ }
33006
+ var CORE_REFRESH_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "LAYOUT.md", "topics/scoring.md"]);
33007
+ var CORE_ADD_ONLY_FILES = /* @__PURE__ */ new Set(["media/_index.md", "media/README.md", "profiles/default/brand.md"]);
33008
+ function classifyCoreFile(rel) {
33009
+ const parts = rel.split("/");
33010
+ if (parts[0] === ".git")
33011
+ return "skip";
33012
+ if (CORE_ADD_ONLY_FILES.has(rel))
33013
+ return "add";
33014
+ if (CORE_REFRESH_FILES.has(rel))
33015
+ return "refresh";
33016
+ if (parts[0] === "workbench")
33017
+ return "refresh";
33018
+ if (parts[0] === "platforms" && rel.endsWith(".md"))
33019
+ return "refresh";
33020
+ if (basename(rel) === "_template.md" || parts.includes("_template"))
33021
+ return "refresh";
33022
+ return "skip";
33023
+ }
33024
+ function walkCoreFiles(core, relDir = "") {
33025
+ const out = [];
33026
+ const absDir = join3(core, relDir);
33027
+ if (!isDir(absDir))
33028
+ return out;
33029
+ for (const name of readdirSync(absDir)) {
33030
+ if (name === ".git" || name === ".DS_Store")
33031
+ continue;
33032
+ const rel = relDir ? `${relDir}/${name}` : name;
33033
+ if (isDir(join3(core, rel)))
33034
+ out.push(...walkCoreFiles(core, rel));
33035
+ else
33036
+ out.push(rel);
33037
+ }
33038
+ return out;
33039
+ }
33040
+ function mergeEngineJson(engine, core) {
33041
+ const engPath = join3(engine, "engine.json");
33042
+ const corePath = join3(core, "engine.json");
33043
+ if (!existsSync3(engPath) || !existsSync3(corePath))
33044
+ return [];
33045
+ let eng;
33046
+ let cor;
33047
+ try {
33048
+ eng = JSON.parse(readFileSync2(engPath, "utf8"));
33049
+ cor = JSON.parse(readFileSync2(corePath, "utf8"));
33050
+ } catch {
33051
+ return [];
33052
+ }
33053
+ const engPaths = eng.paths ?? (eng.paths = {});
33054
+ const corPaths = cor.paths ?? {};
33055
+ const added = [];
33056
+ for (const [key, value] of Object.entries(corPaths)) {
33057
+ if (!(key in engPaths)) {
33058
+ engPaths[key] = value;
33059
+ added.push(`paths.${key}`);
33060
+ }
33061
+ }
33062
+ if (added.length)
33063
+ writeFileSync2(engPath, `${JSON.stringify(eng, null, 2)}
33064
+ `);
33065
+ return added;
33066
+ }
33067
+ function updateEngine(rootOrEngine, moduleUrl, { yes = false } = {}) {
33068
+ const engine = resolveEngine(rootOrEngine);
33069
+ if (!isEngine(engine)) {
33070
+ return {
33071
+ engine,
33072
+ action: "preview",
33073
+ valid: false,
33074
+ code: "NOT_AN_ENGINE",
33075
+ errors: [`${engine} is not a Content Engine. Run engine init first, or pass --engine <path>.`]
33076
+ };
33077
+ }
33078
+ const core = ensureCoreCheckout({ update: true });
33079
+ const refresh = [];
33080
+ const add = [];
33081
+ for (const rel of walkCoreFiles(core)) {
33082
+ const cls = classifyCoreFile(rel);
33083
+ if (cls === "refresh")
33084
+ refresh.push(rel);
33085
+ else if (cls === "add" && !existsSync3(join3(engine, rel)))
33086
+ add.push(rel);
33087
+ }
33088
+ if (!yes) {
33089
+ return {
33090
+ engine,
33091
+ action: "preview",
33092
+ source: contentEngineRepoUrl(),
33093
+ willRefresh: refresh,
33094
+ willAdd: add,
33095
+ next: "Re-run with --yes to apply. Vault data (profiles, needs, runs, media files) is never touched."
33096
+ };
33097
+ }
33098
+ const copy = (list) => {
33099
+ for (const rel of list) {
33100
+ const dest = join3(engine, rel);
33101
+ mkdirSync2(dirname3(dest), { recursive: true });
33102
+ copyFileSync(join3(core, rel), dest);
33103
+ }
33104
+ return list;
33105
+ };
33106
+ const refreshed = copy(refresh);
33107
+ const added = copy(add);
33108
+ const enginePaths = mergeEngineJson(engine, core);
33109
+ const workbenchBuild = buildWorkbench(engine);
33110
+ return {
33111
+ ...validateEngine(engine, moduleUrl),
33112
+ engine,
33113
+ action: "applied",
33114
+ source: contentEngineRepoUrl(),
33115
+ refreshed,
33116
+ added,
33117
+ enginePaths,
33118
+ workbenchBuild
32996
33119
  };
32997
33120
  }
32998
33121
  function detectEngines() {
@@ -33230,7 +33353,7 @@ function buildV1Cli() {
33230
33353
  const command = args.at(-1);
33231
33354
  print({ action: "detect", engines: detectEngines() }, commandWantsJson(command));
33232
33355
  });
33233
- 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) => {
33356
+ jsonOption(engine.command("init").description("Clone public Content Engine core into Content Engine/ (never overwrites an existing engine). Prefer: git clone https://github.com/murphywuwu/content-engine.git").option("--root <path>", "Parent directory or Content Engine path", defaultEngineRoot()).option("--yes", "Clone core into place").option("--serve", "Also start the workbench server").option("--no-open", "Do not open a browser when starting the workbench")).action(async (...args) => {
33234
33357
  const command = args.at(-1);
33235
33358
  const opts = command.opts();
33236
33359
  const result = await initEngine(String(opts.root), CLI_MODULE_URL, {
@@ -33280,25 +33403,38 @@ function buildV1Cli() {
33280
33403
  if (!report.valid)
33281
33404
  throw new Error(report.errors[0] ?? "engine validate failed");
33282
33405
  });
33283
- 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) => {
33406
+ jsonOption(engine.command("serve").description("Rebuild workbench and start on :8765 (does not overwrite local workbench unless --sync-workbench)").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot()).option("--no-open", "Do not open a browser").option("--sync-workbench", "Refresh workbench runtime files from the CLI package")).action(async (...args) => {
33284
33407
  const command = args.at(-1);
33285
33408
  const opts = command.opts();
33286
33409
  const enginePath = resolveEngine(String(opts.engine));
33287
- const started = await startWorkbench(enginePath, CLI_MODULE_URL, { noOpen: opts.noOpen === true });
33410
+ const started = await startWorkbench(enginePath, CLI_MODULE_URL, {
33411
+ noOpen: opts.noOpen === true,
33412
+ syncWorkbench: opts.syncWorkbench === true
33413
+ });
33288
33414
  print({ action: "serve", engine: enginePath, ...started }, commandWantsJson(command));
33289
33415
  if (!started.ok)
33290
33416
  throw new Error("message" in started ? String(started.message) : "engine serve failed");
33291
33417
  });
33292
- 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) => {
33418
+ jsonOption(engine.command("build").description("Rebuild workbench/index.html (does not overwrite local workbench unless --sync-workbench)").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot()).option("--sync-workbench", "Refresh workbench runtime files from the CLI package")).action((...args) => {
33293
33419
  const command = args.at(-1);
33294
33420
  const opts = command.opts();
33295
33421
  const enginePath = resolveEngine(String(opts.engine));
33296
- const workbench = ensureWorkbench(enginePath, CLI_MODULE_URL, { update: true });
33422
+ const sync = wantWorkbenchSync(opts.syncWorkbench === true ? true : void 0);
33423
+ const workbench = ensureWorkbench(enginePath, CLI_MODULE_URL, { update: sync });
33297
33424
  const built = buildWorkbench(enginePath);
33298
- print({ action: "build", engine: enginePath, workbench, ...built }, commandWantsJson(command));
33425
+ print({ action: "build", engine: enginePath, workbench, syncWorkbench: sync, ...built }, commandWantsJson(command));
33299
33426
  if (!built.ok)
33300
33427
  throw new Error(built.message);
33301
33428
  });
33429
+ jsonOption(engine.command("update").description("Sync core rules from the template (CLAUDE.md, workbench, platforms, templates) and add new scaffolds; never touches your vault data. Preview first; apply with --yes.").option("--engine <path>", "Content Engine path or parent", defaultEngineRoot()).option("--yes", "Apply the update (default is a dry-run preview)")).action((...args) => {
33430
+ const command = args.at(-1);
33431
+ const opts = command.opts();
33432
+ const result = updateEngine(String(opts.engine), CLI_MODULE_URL, { yes: opts.yes === true });
33433
+ print(result, commandWantsJson(command));
33434
+ const code = result.code;
33435
+ if (code)
33436
+ throw new Error(result.errors?.[0] ?? code);
33437
+ });
33302
33438
  const auth = program.command("auth").description("Manage the shared EasySociable credential");
33303
33439
  jsonOption(auth.command("status").description("Show authentication status")).action(async (...args) => {
33304
33440
  const command = args.at(-1);