@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
@@ -1,40 +0,0 @@
1
- # Engine layout
2
-
3
- The skill creates one English root:
4
-
5
- ```text
6
- <parent>/Content Engine/
7
- ```
8
-
9
- `--root` is the **parent** directory, or the `Content Engine/` path itself.
10
-
11
- ```json
12
- {
13
- "schemaVersion": "content-engine.v1",
14
- "locale": "en",
15
- "root": "Content Engine",
16
- "paths": { "runs": "runs", "exports": "exports", "workbench": "workbench" }
17
- }
18
- ```
19
-
20
- Folders: `accounts`, `inbox`, `swipe`, `topics`, `discovery`, `raw`, `profiles`, `published`, `wiki`, `platforms`, `runs`, `exports`, `workbench`.
21
-
22
- Platform craft files: `x.md`, `ig.md` (`instagram` → `ig.md`), `tiktok.md`, `linkedin.md`, `threads.md`, `xiaohongshu.md`.
23
-
24
- ## Workbench
25
-
26
- Copied from the skill package **only when missing**. Never overwrite an existing `workbench/template.html`.
27
-
28
- ```text
29
- http://127.0.0.1:8765/workbench/
30
- ```
31
-
32
- Rebuild with `python3 workbench/build.py` after markdown writes.
33
-
34
- ## Export path
35
-
36
- ```text
37
- Content Engine/exports/<run-slug>/01.png
38
- ```
39
-
40
- Exports are **not** nested under `runs/<slug>/`. See `references/RUN_LAYOUT.md`.
@@ -1,16 +0,0 @@
1
- # Local voice vs hosted Profile
2
-
3
- | Concept | Where | When | Purpose |
4
- |---|---|---|---|
5
- | **Local voice** | `profiles/<id>/voice.md` | First draft (optional) | Identity + tone for the Agent |
6
- | **Local profile** | `profiles/<id>/` | Multi-brand | Pillars + audience + voice |
7
- | **Hosted Brand Profile** | Gateway (`easysociable profiles …`) | First render | Visual identity: Theme, logo, quotas |
8
-
9
- ## Rules
10
-
11
- 1. **Setup does not create a hosted Profile.**
12
- 2. **Local voice is optional.** Ask once; user may skip.
13
- 3. **Hosted Profile is required for render**, owned by Template/Slideshow skills.
14
- 4. Never store API keys, passwords or Stripe tokens in `profiles/` or `accounts/`.
15
-
16
- When writing `draft.md` or pack slots, read the resolved voice file and audience **core + this platform**.
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
4
- import { join, relative, resolve, sep } from 'node:path'
5
-
6
- const skillRoot = resolve(new URL('..', import.meta.url).pathname)
7
- const include = ['SKILL.md', 'INSTALL.md', 'scripts', 'references', 'workflows', 'platforms', 'fixtures', 'starter', 'workbench']
8
-
9
- function parseArgs(argv) {
10
- let output = ''
11
- let force = false
12
- for (let i = 0; i < argv.length; i += 1) {
13
- if (argv[i] === '--output') output = argv[++i] ?? ''
14
- else if (argv[i] === '--force') force = true
15
- else if (argv[i] === '--help' || argv[i] === '-h') return { help: true }
16
- else throw new Error(`Unknown argument: ${argv[i]}`)
17
- }
18
- return { output, force }
19
- }
20
-
21
- try {
22
- const options = parseArgs(process.argv.slice(2))
23
- if (options.help) {
24
- process.stdout.write('Usage: node package-skill.mjs --output <destination-dir> [--force]\n')
25
- process.exit(0)
26
- }
27
- if (!options.output) throw new Error('--output is required')
28
-
29
- const dest = resolve(options.output)
30
- const sourceToDest = relative(skillRoot, dest)
31
- const destToSource = relative(dest, skillRoot)
32
- const touchesSource = dest === skillRoot
33
- || (!sourceToDest.startsWith(`..${sep}`) && sourceToDest !== '..')
34
- || (!destToSource.startsWith(`..${sep}`) && destToSource !== '..')
35
- if (touchesSource) throw new Error('--output must be outside the skill source directory')
36
- if (existsSync(dest) && !options.force) throw new Error('Output already exists; pass --force to replace it')
37
- if (existsSync(dest)) rmSync(dest, { recursive: true, force: true })
38
- mkdirSync(dest, { recursive: true })
39
-
40
- for (const name of include) {
41
- cpSync(join(skillRoot, name), join(dest, name), { recursive: true })
42
- }
43
-
44
- process.stdout.write(`Packaged skill → ${dest}\n`)
45
- }
46
- catch (error) {
47
- console.error(error instanceof Error ? error.message : String(error))
48
- process.exitCode = 1
49
- }
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs'
4
- import { homedir } from 'node:os'
5
- import { basename, join, resolve } from 'node:path'
6
- import { ENGINE_ROOT, filesFor } from './starter.mjs'
7
- import { buildWorkbench, ensureWorkbench, python3Bin, startWorkbench } from './workbench.mjs'
8
-
9
- function parseArgs(argv) {
10
- const options = {
11
- root: '', engine: '', apply: false, validate: false, preview: false,
12
- detect: false, noOpen: false, json: false, workbench: false,
13
- }
14
- for (let i = 0; i < argv.length; i += 1) {
15
- const arg = argv[i]
16
- if (arg === '--root') options.root = argv[++i] ?? ''
17
- else if (arg === '--engine') options.engine = argv[++i] ?? ''
18
- else if (arg === '--apply') options.apply = true
19
- else if (arg === '--validate') options.validate = true
20
- else if (arg === '--preview') options.preview = true
21
- else if (arg === '--detect') options.detect = true
22
- else if (arg === '--no-open') options.noOpen = true
23
- else if (arg === '--workbench') options.workbench = true
24
- else if (arg === '--json') options.json = true
25
- else if (arg === '--help' || arg === '-h') options.help = true
26
- else throw new Error(`Unknown argument: ${arg}`)
27
- }
28
- return options
29
- }
30
-
31
- function output(value, json) {
32
- process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : `${value.message ?? value}\n`)
33
- }
34
-
35
- function isDir(path) {
36
- return existsSync(path) && statSync(path).isDirectory()
37
- }
38
-
39
- function isEngine(path) {
40
- return isDir(path) && existsSync(join(path, 'CLAUDE.md')) && isDir(join(path, 'runs'))
41
- }
42
-
43
- function resolveEngine(rootOrEngine) {
44
- const absolute = resolve(rootOrEngine)
45
- if (isEngine(absolute) || basename(absolute) === ENGINE_ROOT) return absolute
46
- return join(absolute, ENGINE_ROOT)
47
- }
48
-
49
- function searchRoots() {
50
- const extra = (process.env.EASYSOCIABLE_ENGINE_SEARCH_ROOTS || '')
51
- .split(process.platform === 'win32' ? ';' : ':')
52
- .map((item) => item.trim())
53
- .filter(Boolean)
54
- const home = homedir()
55
- return [...extra, join(home, 'Documents'), home]
56
- }
57
-
58
- function detectEngines() {
59
- const found = []
60
- for (const root of searchRoots()) {
61
- if (!isDir(root)) continue
62
- const direct = join(root, ENGINE_ROOT)
63
- if (isEngine(direct) || isDir(direct)) found.push(direct)
64
- try {
65
- for (const name of readdirSync(root)) {
66
- const child = join(root, name, ENGINE_ROOT)
67
- if (isDir(child)) found.push(child)
68
- }
69
- }
70
- catch {
71
- continue
72
- }
73
- }
74
- return [...new Set(found)]
75
- }
76
-
77
- function inventory(engine) {
78
- const files = filesFor()
79
- const missing = []
80
- const existing = []
81
- for (const relative of Object.keys(files)) {
82
- const path = join(engine, relative)
83
- ;(existsSync(path) ? existing : missing).push(relative)
84
- }
85
- return {
86
- engine,
87
- engineExists: isDir(engine),
88
- rootName: ENGINE_ROOT,
89
- missing,
90
- existing,
91
- starterComplete: missing.length === 0,
92
- }
93
- }
94
-
95
- function validateRoot(rootOrEngine, { strict = false } = {}) {
96
- const engine = resolveEngine(rootOrEngine)
97
- const parent = resolve(engine, '..')
98
- const errors = []
99
- if (!existsSync(parent) || !statSync(parent).isDirectory()) {
100
- errors.push('Parent directory does not exist')
101
- return { engine, valid: false, errors, created: [] }
102
- }
103
- const result = inventory(engine)
104
- if (existsSync(engine) && !isDir(engine)) errors.push('Content Engine exists but is not a directory')
105
- if (strict) {
106
- if (!result.engineExists) errors.push('Content Engine/ folder does not exist')
107
- else if (result.missing.length > 0) {
108
- errors.push(`Starter incomplete: ${result.missing.length} file(s) missing (use --apply to add only missing files)`)
109
- }
110
- }
111
- return { ...result, valid: errors.length === 0, errors, created: [] }
112
- }
113
-
114
- async function applyStarter(rootOrEngine, noOpen) {
115
- const engine = resolveEngine(rootOrEngine)
116
- const checked = validateRoot(engine)
117
- if (checked.code) return { ...checked, created: [] }
118
- mkdirSync(engine, { recursive: true })
119
- const report = inventory(engine)
120
- const created = []
121
- const files = filesFor()
122
- for (const relative of report.missing) {
123
- const path = join(engine, relative)
124
- mkdirSync(join(path, '..'), { recursive: true })
125
- writeFileSync(path, files[relative], { encoding: 'utf8', flag: 'wx' })
126
- created.push(relative)
127
- }
128
- const workbench = ensureWorkbench(engine)
129
- const python = python3Bin()
130
- if (!python) {
131
- return {
132
- ...validateRoot(engine),
133
- created,
134
- workbench,
135
- action: 'applied',
136
- valid: false,
137
- code: 'PYTHON3_REQUIRED',
138
- errors: ['python3 is required for the workbench. Install Python 3 and retry. Starter files were written; workbench was not started.'],
139
- }
140
- }
141
- const built = buildWorkbench(engine)
142
- let started = { skipped: true, build: built }
143
- if (!noOpen && process.env.EASYSOCIABLE_NO_OPEN !== 'true') {
144
- started = await startWorkbench(engine, { noOpen: false })
145
- }
146
- return { ...validateRoot(engine), created, workbench, workbenchBuild: built, workbenchStart: started, action: 'applied' }
147
- }
148
-
149
- function printHelp() {
150
- process.stdout.write(`Usage:
151
- node setup.mjs --detect [--json]
152
- node setup.mjs --root <parent-or-engine> [--preview|--apply|--validate] [--workbench] [--no-open] [--json]
153
-
154
- English Content Engine/ only. Existing files are never overwritten.
155
- `)
156
- }
157
-
158
- try {
159
- const options = parseArgs(process.argv.slice(2))
160
- if (options.help) {
161
- printHelp()
162
- process.exit(0)
163
- }
164
- if (options.detect) {
165
- output({ action: 'detect', engines: detectEngines() }, options.json)
166
- process.exit(0)
167
- }
168
- const target = options.engine || options.root
169
- if (!target) {
170
- output({ code: 'ROOT_REQUIRED', message: 'Choose a parent directory or Content Engine path with --root.', created: false }, options.json)
171
- process.exitCode = 2
172
- }
173
- else if (options.apply) {
174
- const applied = await applyStarter(target, options.noOpen)
175
- output(applied, options.json)
176
- if (applied.code) process.exitCode = 2
177
- }
178
- else if (options.workbench) {
179
- const engine = resolveEngine(target)
180
- if (!isEngine(engine) && !isDir(engine)) {
181
- output({ code: 'ENGINE_MISSING', message: 'Content Engine/ not found. Run --apply first.', created: false }, options.json)
182
- process.exitCode = 2
183
- }
184
- else {
185
- const started = await startWorkbench(engine, { noOpen: options.noOpen })
186
- output({ action: 'workbench', ...started, engine }, options.json)
187
- if (!started.ok) process.exitCode = 2
188
- }
189
- }
190
- else {
191
- const report = validateRoot(target, { strict: options.validate })
192
- const action = options.validate ? 'validated' : 'preview'
193
- output({ ...report, action, created: [] }, options.json)
194
- if (report.code || (options.validate && !report.valid)) process.exitCode = 2
195
- }
196
- }
197
- catch (error) {
198
- output({ code: 'CONTENT_SETUP_FAILED', message: error instanceof Error ? error.message : String(error), created: false }, true)
199
- process.exitCode = 1
200
- }
@@ -1,82 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
- import { spawnSync } from 'node:child_process'
6
-
7
- const script = new URL('./setup.mjs', import.meta.url).pathname
8
- const fixture = mkdtempSync(join(tmpdir(), 'easysociable-content-'))
9
- const root = join(fixture, 'Workspace')
10
- mkdirSync(root)
11
- const env = { ...process.env, EASYSOCIABLE_NO_OPEN: 'true' }
12
-
13
- function run(args) {
14
- return spawnSync(process.execPath, [script, ...args, '--json'], { encoding: 'utf8', env })
15
- }
16
-
17
- try {
18
- const missingRoot = run(['--preview'])
19
- assert.equal(missingRoot.status, 2)
20
- assert.equal(JSON.parse(missingRoot.stdout).code, 'ROOT_REQUIRED')
21
-
22
- const preview = run(['--root', root, '--preview'])
23
- assert.equal(preview.status, 0)
24
- const previewReport = JSON.parse(preview.stdout)
25
- assert.equal(previewReport.created.length, 0)
26
- assert.equal(previewReport.valid, true)
27
- assert.equal(previewReport.rootName, 'Content Engine')
28
-
29
- const apply = run(['--root', root, '--apply', '--no-open'])
30
- assert.equal(apply.status, 0, apply.stdout + apply.stderr)
31
- const applied = JSON.parse(apply.stdout)
32
- assert.equal(applied.action, 'applied')
33
- assert.equal(applied.created.length > 10, true)
34
- const engine = join(root, 'Content Engine')
35
- assert.equal(readFileSync(join(engine, 'CLAUDE.md'), 'utf8').includes('one platform = one Run') || readFileSync(join(engine, 'CLAUDE.md'), 'utf8').includes('One folder = one platform'), true)
36
- const engineJson = JSON.parse(readFileSync(join(engine, 'engine.json'), 'utf8'))
37
- assert.equal(engineJson.schemaVersion, 'content-engine.v1')
38
- assert.equal(engineJson.root, 'Content Engine')
39
- assert.equal(engineJson.paths.runs, 'runs')
40
- assert.equal(existsSync(join(engine, 'runs', '_template', 'idea.md')), true)
41
- assert.equal(existsSync(join(engine, 'runs', '_template', 'run.md')), false)
42
- assert.equal(existsSync(join(engine, 'platforms', 'ig.md')), true)
43
- assert.equal(existsSync(join(engine, 'discovery', '_template.md')), true)
44
- assert.equal(existsSync(join(engine, 'discovery', '_index.md')), true)
45
- assert.equal(readFileSync(join(engine, 'CLAUDE.md'), 'utf8').includes('§ Discovery'), true)
46
- assert.equal(existsSync(join(engine, 'workbench', 'build.py')), true)
47
- assert.equal(existsSync(join(engine, 'workbench', 'template.html')), true)
48
- assert.equal(existsSync(join(engine, 'workbench', 'index.html')), true)
49
- const sentinel = join(engine, 'runs', 'user-note.md')
50
- writeFileSync(sentinel, 'keep me', 'utf8')
51
- const customTemplate = join(engine, 'workbench', 'template.html')
52
- const before = readFileSync(customTemplate, 'utf8')
53
- writeFileSync(customTemplate, `${before}\n<!-- user-custom -->\n`, 'utf8')
54
- const second = run(['--root', root, '--apply', '--no-open'])
55
- assert.equal(second.status, 0)
56
- assert.equal(readFileSync(sentinel, 'utf8'), 'keep me')
57
- assert.equal(readFileSync(customTemplate, 'utf8').includes('user-custom'), true)
58
- assert.equal(JSON.parse(second.stdout).created.includes('CLAUDE.md'), false)
59
-
60
- const detectRoot = join(fixture, 'DetectRoot')
61
- const detectEngine = join(detectRoot, 'MyFolder', 'Content Engine')
62
- mkdirSync(detectEngine, { recursive: true })
63
- const detect = spawnSync(process.execPath, [script, '--detect', '--json'], {
64
- encoding: 'utf8',
65
- env: { ...env, EASYSOCIABLE_ENGINE_SEARCH_ROOTS: detectRoot },
66
- })
67
- assert.equal(detect.status, 0, detect.stdout + detect.stderr)
68
- const detectReport = JSON.parse(detect.stdout)
69
- assert.equal(detectReport.action, 'detect')
70
- assert.equal(detectReport.engines.includes(detectEngine), true)
71
-
72
- const validateOk = run(['--root', root, '--validate'])
73
- assert.equal(validateOk.status, 0)
74
- rmSync(join(engine, 'engine.json'))
75
- const validateMissing = run(['--root', root, '--validate'])
76
- assert.equal(validateMissing.status, 2)
77
-
78
- console.log('Content Skill setup smoke passed.')
79
- }
80
- finally {
81
- rmSync(fixture, { recursive: true, force: true })
82
- }
@@ -1,18 +0,0 @@
1
- # Discovery
2
-
3
- Discovery is the intermediate layer between watchlist signals and Topics.
4
-
5
- ```text
6
- watchlist → discovery → (explicit promote) → topic → brief
7
- ```
8
-
9
- It makes the source understandable before deciding whether the idea belongs to us.
10
-
11
- - `Source Digest` is a short natural-language note: what happened, the author's argument, support, native language, and limits.
12
- - `Source limits` records what the source does not establish.
13
- - `Decision` stores only workflow status and next action.
14
- - A source with high engagement can still remain `saved` or `rejected`.
15
-
16
- Do not add our audience, product, thesis, ownership, or candidate Topics to a Discovery.
17
- Do not use 5C as the default Discovery reading format.
18
- Discovery does not generate publish-ready copy.
@@ -1,27 +0,0 @@
1
- # Discovery index
2
-
3
- One row = one analyzed external signal from a watchlist scan. Discovery is not a topic queue.
4
-
5
- Newest on top.
6
-
7
- | id | date | profile | platform | source_handle | source_url | source_claim | source_role | status | next_action | topic_ids | file |
8
- |----|------|---------|----------|---------------|------------|--------------|-------------|--------|-------------|-----------|------|
9
- | -- | ---- | ------- | -------- | ------------- | ---------- | ------------ | ----------- | ------ | ----------- | --------- | ---- |
10
-
11
- ## Status values
12
-
13
- | status | Meaning |
14
- |--------|---------|
15
- | `unread` | Signal captured; digest not read |
16
- | `read` | Source Digest completed |
17
- | `saved` | Useful reference; no Topic decision yet |
18
- | `promoted` | User explicitly promoted it to Topic |
19
- | `rejected` | Not relevant, provable, or ownable |
20
-
21
- ## Rules
22
-
23
- - Watchlist heat is source context, not our evidence.
24
- - New Discovery records contain only Source Digest, source limits, and workflow state.
25
- - Do not create a Topic during watchlist scanning; Topic creation requires explicit promotion.
26
- - Do not store our audience, product, ownership, or candidate opportunities inside Discovery.
27
- - 5C belongs to Topic evaluation and is never the default Discovery reading surface.
@@ -1,41 +0,0 @@
1
- ---
2
- id: D-YYYYMMDD-XX
3
- date: YYYY-MM-DD
4
- status: read
5
- profile: default
6
- platform: x
7
- source_role: demand | ally | foil | mechanism
8
- ---
9
-
10
- # Discovery
11
-
12
- One sentence describing the external signal in the source's terms. Do not turn it into our topic here.
13
-
14
- ## Source
15
-
16
- - **handle:**
17
- - **url:**
18
- - **captured_at:**
19
- - **engagement snapshot:** likes / replies / bookmarks / views @ time
20
- - **why on watchlist:**
21
-
22
- ## Source Digest
23
-
24
- ### In one sentence
25
-
26
- ### What happened
27
-
28
- ### What the author is arguing
29
-
30
- ### How the author supports it
31
-
32
- ### The author's own language
33
-
34
- ### Source limits
35
-
36
- What the source does not establish. Keep this short.
37
-
38
- ## Decision
39
-
40
- - **status:** unread | read | saved | promoted | rejected
41
- - **next_action:** none | revisit | promote_to_topic
@@ -1,13 +0,0 @@
1
- # Profiles index
2
-
3
- Reusable **content profiles**: pillars + audience + voice.
4
- Accounts point here; do not copy profile bodies into account files.
5
-
6
- | id | path | voice | one_liner | accounts |
7
- |----|------|-------|-----------|----------|
8
- | default | profiles/default/ | `profiles/default/voice.md` | Replace this one-liner with the profile’s job | your-handle |
9
-
10
- ## Voice column
11
-
12
- - Empty or `profile` → load `profiles/<id>/voice.md`
13
- - Explicit path → load that file instead
@@ -1,25 +0,0 @@
1
- # topics/
2
-
3
- Topic system: **what to produce next**, after an external signal has been understood and converted into an owned opportunity.
4
-
5
- | Path | Role |
6
- |------|------|
7
- | `_index.md` | Queue registry (one row per topic + `item` link + source engagement) |
8
- | `items/T-*.md` | One file per opportunity (context, audience, conflict, insight, evidence) |
9
- | `WATCHLIST.md` | Watchlist router (per-profile scheme B) |
10
- | `watchlists/<profile_id>.md` | Profile-scoped radar |
11
- | `watchlists/_shared.md` | Shared radar for all profiles |
12
- | `scoring.md` | Opportunity dimensions and dual gate |
13
- | `_template-item.md` | Template for new items |
14
-
15
- Flow:
16
-
17
- ```text
18
- watchlist → Discovery (Source Digest)
19
- → user explicitly promotes
20
- → `_index` + `items/T-…` (opportunity 5-dim)
21
- → ready only if opp≥7.5 and audience≥6
22
- → user opens run → `status=promoted`
23
- ```
24
-
25
- Cap **5 produce** per promotion session. Watchlist scan itself creates Discovery records, not Topics.
@@ -1,65 +0,0 @@
1
- # Watchlist (router)
2
-
3
- **Scheme B:** one radar table **per profile**, not one global table.
4
-
5
- No X Private List required. Agent updates files under `topics/watchlists/`; user works in chat only.
6
-
7
- ## File map
8
-
9
- | Path | Role |
10
- |------|------|
11
- | `topics/WATCHLIST.md` | **This file** — routing, columns, scan rules |
12
- | `topics/watchlists/_shared.md` | Shared rows (`*`) — merge into every scan |
13
- | `topics/watchlists/<profile_id>.md` | Profile-scoped radar (must match `profiles/_index` id) |
14
- | `topics/watchlists/README.md` | Short folder note |
15
-
16
- ### Profiles with a watchlist file today
17
-
18
- | profile_id | file |
19
- |------------|------|
20
- | default | `watchlists/default.md` |
21
-
22
- New profile → create `watchlists/<id>.md` (copy empty table header from `_shared.md` or an existing file).
23
-
24
- ## Columns (in each table file)
25
-
26
- | Column | Meaning |
27
- |--------|---------|
28
- | `#` | Id within that file (`#1…` profile-local; `#S-01…` in `_shared`) |
29
- | **`platform`** | Network of this handle + where Agent scans: `x` \| `tiktok` \| `linkedin` \| `ig` \| `youtube` \| `xhs` \| `threads` \| … |
30
- | `handle` | Platform-native id without `@` |
31
- | `niche_tag` | Topic/product tag — not a substitute for `platform` |
32
- | `why_on_list` | One line |
33
- | `weight` | 0.5–1.5 |
34
- | `status` | active / paused / removed |
35
-
36
- **No `profiles` column in the table** — the **filename** is the profile scope.
37
- Shared across profiles → put the row in `_shared.md` (same as `*`).
38
- Same handle needed by two profiles only (not all) → **copy the row into both files** (or later a small group file; avoid until needed).
39
-
40
- ## Resolve profile for scan / add (required)
41
-
42
- 1. User names **profile_id** → use it.
43
- 2. User names **account** handle → resolve via `accounts/_index.md` → `profile_id`.
44
- 3. User names **neither** → **do not default**. List active profiles from `profiles/_index.md` and **ask which profile** (or account).
45
- 4. Load rows = **`watchlists/_shared.md` (active)** ∪ **`watchlists/<profile_id>.md` (active)**.
46
- 5. Filter **`platform`**: user-specified, else default **`x`**; `全部平台` = no platform filter.
47
- 6. Create **Discovery** records first; do not create Topics during the scan. Cap **5** new **`produce`** topics only when the user later promotes. Sort watchlist rows by weight (≥1.3 first).
48
- 7. Wrong network for a row → never (e.g. no X tools on `platform=tiktok`).
49
-
50
- ## Maintenance
51
-
52
- | User says | Agent does |
53
- |-----------|------------|
54
- | 把 @foo 加进 watchlist | Resolve/ask **profile** + **platform**; append row to that profile file (or `_shared` if user says 全 profile / shared) |
55
- | 去掉 #n / @foo | Edit the correct file; if ambiguous, ask |
56
- | 新 profile 建雷达 | Create `watchlists/<id>.md` |
57
-
58
- ## Scan rules (also CLAUDE § Discovery)
59
-
60
- - Create one Discovery per meaningful signal; discard noise before analysis.
61
- - Watchlist scan creates **Discovery** records first; it does not create Topics directly.
62
- - Write a Source Digest + source limits only. Do not add our audience, product, ownership, or candidate Topics.
63
- - Keep source engagement as context on the Discovery file; copy `src_*` to `topics/_index.md` only after promotion.
64
- - Report `unread / read / saved / promoted / rejected` counts after the batch.
65
-
@@ -1,90 +0,0 @@
1
- ---
2
- id: T-YYYYMMDD-XX
3
- date: YYYY-MM-DD
4
- status: scored
5
- account: your-handle
6
- profile: default
7
- platform: x
8
- pillar: P1
9
- source_type: watchlist | inbox | user | discovery
10
- purpose: produce | library | discard
11
- scout_label: demand | ally | foil | craft_only
12
- ---
13
-
14
- # Opportunity
15
-
16
- One sentence — what's the content opportunity, not a title.
17
-
18
- ## Source
19
-
20
- - **type:** watchlist | inbox | user | discovery
21
- - **url:**
22
- - **handle:**
23
- - **discovery_id:** (only if promoted from D-…; else empty)
24
- - **capture_id:** (only if linked to inbox; else empty)
25
- - **raw:** (only if exists)
26
- - **engagement snapshot:** likes / replies / bookmarks / views @ time
27
-
28
- ## Scout
29
-
30
- - **purpose:** produce | library
31
- - **scout_label:** demand | ally | foil | craft_only
32
- - **scout_note:** (one line — why not discard)
33
-
34
- ## Context
35
-
36
- - **what_changed:**
37
- - **why_now:**
38
-
39
- ## Audience
40
-
41
- - **who:**
42
- - **current_belief:**
43
- - **icp_checks:** (pain / buyer shape / wedge — note which hit, from profile)
44
-
45
- ## Conflict
46
-
47
- - **old_model:**
48
- - **new_reality:**
49
- - **belief_gap:**
50
-
51
- ## Insight
52
-
53
- - **potential_insight:**
54
- - **why_nonobvious:**
55
-
56
- ## Evidence
57
-
58
- - **available:**
59
- - **type:** data | example | case | mechanism | expert | observation
60
- - **gaps:**
61
-
62
- ## Score (opportunity)
63
-
64
- | dim | score 0-10 | note |
65
- |-----|------------|------|
66
- | context | | |
67
- | audience | | |
68
- | conflict | | |
69
- | insight | | |
70
- | evidence | | |
71
-
72
- **opp_score:** (weighted per `topics/scoring.md`)
73
- **audience_floor:** (hard check ≥ 6)
74
-
75
- ## Profile fit
76
-
77
- - pillars:
78
- - audience_pain:
79
- - risk:
80
-
81
- ## Suggested production (optional, not binding)
82
-
83
- - swipe: none | S…
84
- - atoms: none | …
85
- - claim: none | C…
86
-
87
- ## Decision
88
-
89
- - next: open_run | hold | kill | library_ingest | research_evidence
90
- - run_id: (filled when promoted)