@easysociable/cli 0.1.1 → 0.1.3
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.
- package/README.md +29 -18
- package/content-engine/fixtures/golden-run-en/draft.md +13 -0
- package/content-engine/fixtures/golden-run-en/idea.md +26 -0
- package/content-engine/fixtures/golden-run-en/pack-tiktok.json +11 -0
- package/content-engine/scripts/contracts.mjs +183 -0
- package/content-engine/scripts/export-contracts.mjs +76 -0
- package/content-engine/scripts/fixture.test.mjs +24 -0
- package/content-engine/scripts/starter.mjs +55 -0
- package/content-engine/scripts/validate-export.mjs +42 -0
- package/content-engine/scripts/validate-pack.mjs +48 -0
- package/content-engine/scripts/validate-run.mjs +100 -0
- package/content-engine/scripts/validate.test.mjs +101 -0
- package/content-engine/scripts/workbench.mjs +135 -0
- package/content-engine/starter/CLAUDE.md +756 -0
- package/content-engine/starter/accounts/README.md +9 -0
- package/content-engine/starter/accounts/_index.md +31 -0
- package/content-engine/starter/accounts/overrides/README.md +3 -0
- package/content-engine/starter/discovery/README.md +18 -0
- package/content-engine/starter/discovery/_index.md +27 -0
- package/content-engine/starter/discovery/_template.md +41 -0
- package/content-engine/starter/engine.json +19 -0
- package/content-engine/starter/exports/README.md +28 -0
- package/content-engine/starter/inbox/README.md +9 -0
- package/content-engine/starter/inbox/_index.md +24 -0
- package/content-engine/starter/inbox/entries/README.md +3 -0
- package/content-engine/starter/platforms/README.md +19 -0
- package/content-engine/starter/platforms/ig.md +69 -0
- package/content-engine/starter/platforms/instagram.md +5 -0
- package/content-engine/starter/platforms/linkedin.md +72 -0
- package/content-engine/starter/platforms/threads.md +69 -0
- package/content-engine/starter/platforms/tiktok.md +71 -0
- package/content-engine/starter/platforms/x.md +78 -0
- package/content-engine/starter/platforms/xiaohongshu.md +19 -0
- package/content-engine/starter/profiles/_index.md +13 -0
- package/content-engine/starter/profiles/default/README.md +11 -0
- package/content-engine/starter/profiles/default/audience.md +38 -0
- package/content-engine/starter/profiles/default/pillars.md +33 -0
- package/content-engine/starter/profiles/default/voice.md +24 -0
- package/content-engine/starter/published/_index.md +20 -0
- package/content-engine/starter/raw/README.md +4 -0
- package/content-engine/starter/raw/_index.md +12 -0
- package/content-engine/starter/runs/README.md +22 -0
- package/content-engine/starter/runs/_index.md +41 -0
- package/content-engine/starter/runs/_template/brief.md +66 -0
- package/content-engine/starter/runs/_template/draft.md +11 -0
- package/content-engine/starter/runs/_template/editor.md +18 -0
- package/content-engine/starter/runs/_template/feedback.md +15 -0
- package/content-engine/starter/runs/_template/idea.md +27 -0
- package/content-engine/starter/runs/_template/pack.md +47 -0
- package/content-engine/starter/runs/_template/packet.md +25 -0
- package/content-engine/starter/runs/_template/rubric.md +19 -0
- package/content-engine/starter/swipe/_index.md +27 -0
- package/content-engine/starter/swipe/_template.md +34 -0
- package/content-engine/starter/topics/README.md +25 -0
- package/content-engine/starter/topics/WATCHLIST.md +65 -0
- package/content-engine/starter/topics/_index.md +44 -0
- package/content-engine/starter/topics/_template-item.md +90 -0
- package/content-engine/starter/topics/items/README.md +4 -0
- package/content-engine/starter/topics/scoring.md +201 -0
- package/content-engine/starter/topics/watchlists/README.md +10 -0
- package/content-engine/starter/topics/watchlists/_shared.md +14 -0
- package/content-engine/starter/topics/watchlists/default.md +6 -0
- package/content-engine/starter/wiki/README.md +13 -0
- package/content-engine/starter/wiki/atoms/_index.md +23 -0
- package/content-engine/starter/wiki/atoms/_template.md +31 -0
- package/content-engine/starter/wiki/claims/_index.md +26 -0
- package/content-engine/starter/wiki/claims/_template.md +38 -0
- package/content-engine/starter/workbench/README.md +17 -0
- package/content-engine/workbench/build.py +1074 -0
- package/content-engine/workbench/serve.py +53 -0
- package/content-engine/workbench/template.html +2119 -0
- package/dist/easysociable.js +804 -2903
- package/dist/easysociable.js.map +1 -1
- package/package.json +19 -13
- package/skills/ONBOARDING.md +20 -14
- package/skills/easysociable-content/SKILL.md +38 -102
- package/skills/easysociable-slideshow/SKILL.md +24 -16
- package/skills/easysociable-slideshow/references/VAULT.md +13 -11
- package/skills/easysociable-template/SKILL.md +26 -24
- package/skills/easysociable-template/references/CATALOG_METADATA.md +2 -2
- package/skills/easysociable-template/references/DESIGN_V2_1.md +53 -2
- package/skills/easysociable-template/references/GATEWAY.md +7 -2
- package/skills/easysociable-template/references/PLATFORM_SPECS.md +11 -3
- package/skills/easysociable-template/references/THEME_TOKENS.md +1 -1
- package/skills/easysociable-template/references/fixtures/image-reference.design-plan.json +74 -0
- package/skills/easysociable-template/references/fixtures/image-reference.visual-qa.json +28 -0
- package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
- package/skills/easysociable-template/scripts/validate-design-v2.mjs +97 -0
- package/skills/easysociable-template/workflows/ASSET_PLAN.md +2 -1
- package/skills/easysociable-template/workflows/AUTHORING_FROM_BRIEF.md +4 -4
- package/skills/easysociable-template/workflows/AUTHORING_FROM_IMAGE.md +66 -16
- package/skills/easysociable-template/workflows/AUTHORING_STUDIO.md +28 -15
- package/skills/easysociable-template/workflows/DESIGN_BRIEF.md +4 -3
- package/skills/easysociable-template/workflows/MULTI_PLATFORM_FAMILY.md +81 -0
- package/skills/easysociable-template/workflows/PUBLISH_FROM_DRAFT.md +1 -1
- package/skills/easysociable-template/workflows/VISUAL_QA.md +10 -0
- package/skills/easysociable-content/platforms/claude.md +0 -5
- package/skills/easysociable-content/platforms/codex.md +0 -5
- package/skills/easysociable-content/platforms/hermes.md +0 -5
- package/skills/easysociable-content/platforms/openclaw.md +0 -5
- package/skills/easysociable-content/references/CLI.md +0 -27
- package/skills/easysociable-content/references/MCP.md +0 -20
- package/skills/easysociable-content/references/PAGE_PACK.md +0 -6
- package/skills/easysociable-content/references/TROUBLESHOOTING.md +0 -11
- package/skills/easysociable-content/references/VAULT.md +0 -20
- package/skills/easysociable-content/scripts/setup.mjs +0 -156
- package/skills/easysociable-content/scripts/setup.test.mjs +0 -71
- package/skills/easysociable-content/scripts/starter.mjs +0 -53
- package/skills/easysociable-content/workflows/ANALYSIS_AND_COVERAGE.md +0 -8
- package/skills/easysociable-content/workflows/CONTENT_EXPORT.md +0 -19
- package/skills/easysociable-content/workflows/DEVICE_TASKS.md +0 -5
- package/skills/easysociable-content/workflows/EXPERIMENTS.md +0 -6
- package/skills/easysociable-content/workflows/MCP_FIRST.md +0 -10
- package/skills/easysociable-content/workflows/PUBLICATION.md +0 -5
- package/skills/easysociable-content/workflows/TOPIC_DRAFT.md +0 -5
- package/skills/easysociable-content/workflows/WORKING_DRAFT.md +0 -10
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdirSync, mkdtempSync, 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
|
+
import { validateContentPack } from './contracts.mjs'
|
|
7
|
+
import { validateExportDirectory } from './export-contracts.mjs'
|
|
8
|
+
|
|
9
|
+
const root = new URL('.', import.meta.url).pathname
|
|
10
|
+
const validatePack = join(root, 'validate-pack.mjs')
|
|
11
|
+
const validateRun = join(root, 'validate-run.mjs')
|
|
12
|
+
const fixture = mkdtempSync(join(tmpdir(), 'easysociable-validate-'))
|
|
13
|
+
|
|
14
|
+
function run(script, args) {
|
|
15
|
+
return spawnSync(process.execPath, [script, ...args, '--json'], { encoding: 'utf8' })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ideaMd({ platform = 'tiktok', status = 'pack-ready' } = {}) {
|
|
19
|
+
return `---
|
|
20
|
+
run_id: RUN-demo
|
|
21
|
+
status: ${status}
|
|
22
|
+
created: 2026-08-28
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Idea
|
|
26
|
+
|
|
27
|
+
- **one_liner:** Demo
|
|
28
|
+
- **primary_platform:** ${platform}
|
|
29
|
+
- **platforms:** ${platform}
|
|
30
|
+
- **swipe_id:** none
|
|
31
|
+
- **atoms:** none
|
|
32
|
+
- **claim_id:** none
|
|
33
|
+
`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const tiktokPack = {
|
|
37
|
+
schemaVersion: 'content-pack.v1',
|
|
38
|
+
platform: 'tiktok',
|
|
39
|
+
title: 'Demo',
|
|
40
|
+
pages: [
|
|
41
|
+
{ role: 'hook', pageTemplateId: 'tpl_hook', slots: { text_0: 'a' } },
|
|
42
|
+
{ role: 'value', pageTemplateId: 'tpl_value', slots: { text_0: 'b' } },
|
|
43
|
+
{ role: 'value', pageTemplateId: 'tpl_value', slots: { text_0: 'c' } },
|
|
44
|
+
{ role: 'cta', pageTemplateId: 'tpl_cta', slots: { text_0: 'd' } },
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const ok = validateContentPack(tiktokPack)
|
|
50
|
+
assert.equal(ok.valid, true)
|
|
51
|
+
|
|
52
|
+
const short = validateContentPack({ ...tiktokPack, pages: tiktokPack.pages.slice(0, 3) })
|
|
53
|
+
assert.equal(short.valid, false)
|
|
54
|
+
|
|
55
|
+
const forbidden = validateContentPack({ ...tiktokPack, deck_template_id: 'x' })
|
|
56
|
+
assert.equal(forbidden.valid, false)
|
|
57
|
+
|
|
58
|
+
const badSlot = validateContentPack({ ...tiktokPack, pages: [{ ...tiktokPack.pages[0], slots: { text_0: [] } }, ...tiktokPack.pages.slice(1)] })
|
|
59
|
+
assert.equal(badSlot.valid, false)
|
|
60
|
+
|
|
61
|
+
const missingTemplate = validateContentPack({ ...tiktokPack, pages: [{ role: 'hook', slots: {} }, ...tiktokPack.pages.slice(1)] })
|
|
62
|
+
assert.equal(missingTemplate.valid, false)
|
|
63
|
+
|
|
64
|
+
const packFile = join(fixture, 'pack-tiktok.json')
|
|
65
|
+
writeFileSync(packFile, JSON.stringify(tiktokPack), 'utf8')
|
|
66
|
+
const packCli = run(validatePack, ['--file', packFile, '--platform', 'tiktok'])
|
|
67
|
+
assert.equal(packCli.status, 0)
|
|
68
|
+
|
|
69
|
+
const runDir = join(fixture, 'demo-run')
|
|
70
|
+
mkdirSync(runDir)
|
|
71
|
+
writeFileSync(join(runDir, 'idea.md'), ideaMd(), 'utf8')
|
|
72
|
+
writeFileSync(join(runDir, 'draft.md'), '# Draft\n', 'utf8')
|
|
73
|
+
writeFileSync(join(runDir, 'pack-tiktok.json'), JSON.stringify(tiktokPack), 'utf8')
|
|
74
|
+
|
|
75
|
+
const runCli = run(validateRun, ['--run', runDir, '--require-pack'])
|
|
76
|
+
assert.equal(runCli.status, 0)
|
|
77
|
+
const runReport = JSON.parse(runCli.stdout)
|
|
78
|
+
assert.equal(runReport.valid, true)
|
|
79
|
+
|
|
80
|
+
rmSync(join(runDir, 'pack-tiktok.json'))
|
|
81
|
+
const missingPack = run(validateRun, ['--run', runDir, '--require-pack'])
|
|
82
|
+
assert.equal(missingPack.status, 2)
|
|
83
|
+
|
|
84
|
+
const exportDir = join(fixture, 'exported')
|
|
85
|
+
mkdirSync(exportDir)
|
|
86
|
+
writeFileSync(join(exportDir, '01.png'), Buffer.from('89504e470d0a1a0a', 'hex'))
|
|
87
|
+
writeFileSync(join(exportDir, 'manifest.json'), JSON.stringify({ schema: 'template-export.v1', artifacts: [{ pageIndex: 0 }] }))
|
|
88
|
+
assert.equal(validateExportDirectory(exportDir, { expectedPages: 1 }).valid, true)
|
|
89
|
+
|
|
90
|
+
const exportedRun = join(fixture, 'exported-run')
|
|
91
|
+
mkdirSync(exportedRun)
|
|
92
|
+
writeFileSync(join(exportedRun, 'idea.md'), ideaMd({ platform: 'x', status: 'exported' }), 'utf8')
|
|
93
|
+
writeFileSync(join(exportedRun, 'draft.md'), '# Draft\n', 'utf8')
|
|
94
|
+
const exportedCli = run(validateRun, ['--run', exportedRun, '--export', exportDir, '--require-export'])
|
|
95
|
+
assert.equal(exportedCli.status, 0, exportedCli.stdout)
|
|
96
|
+
|
|
97
|
+
console.log('Content Skill validate smoke passed.')
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
rmSync(fixture, { recursive: true, force: true })
|
|
101
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
4
|
+
import { existsSync, mkdirSync, copyFileSync } from 'node:fs'
|
|
5
|
+
import net from 'node:net'
|
|
6
|
+
import { pathToFileURL } from 'node:url'
|
|
7
|
+
import { join, resolve } from 'node:path'
|
|
8
|
+
import { WORKBENCH_FILES, WORKBENCH_SRC } from './starter.mjs'
|
|
9
|
+
|
|
10
|
+
export const WORKBENCH_PORT = 8765
|
|
11
|
+
export const WORKBENCH_URL = `http://127.0.0.1:${WORKBENCH_PORT}/workbench/`
|
|
12
|
+
|
|
13
|
+
function output(value, json) {
|
|
14
|
+
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : `${value.message ?? value}\n`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function python3Bin() {
|
|
18
|
+
const result = spawnSync('python3', ['--version'], { encoding: 'utf8' })
|
|
19
|
+
if (result.status === 0) return 'python3'
|
|
20
|
+
return ''
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function portInUse(port = WORKBENCH_PORT) {
|
|
24
|
+
return new Promise((resolvePort) => {
|
|
25
|
+
const socket = net.connect({ port, host: '127.0.0.1' }, () => {
|
|
26
|
+
socket.end()
|
|
27
|
+
resolvePort(true)
|
|
28
|
+
})
|
|
29
|
+
socket.on('error', () => resolvePort(false))
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ensureWorkbench(engine) {
|
|
34
|
+
const destDir = join(engine, 'workbench')
|
|
35
|
+
mkdirSync(destDir, { recursive: true })
|
|
36
|
+
const copied = []
|
|
37
|
+
const skipped = []
|
|
38
|
+
for (const name of WORKBENCH_FILES) {
|
|
39
|
+
const dest = join(destDir, name)
|
|
40
|
+
if (existsSync(dest)) {
|
|
41
|
+
skipped.push(name)
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
copyFileSync(join(WORKBENCH_SRC, name), dest)
|
|
45
|
+
copied.push(name)
|
|
46
|
+
}
|
|
47
|
+
return { destDir, copied, skipped }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildWorkbench(engine) {
|
|
51
|
+
const python = python3Bin()
|
|
52
|
+
if (!python) {
|
|
53
|
+
return { ok: false, code: 'PYTHON3_REQUIRED', message: 'python3 is required for the workbench. Install Python 3 and retry.' }
|
|
54
|
+
}
|
|
55
|
+
const script = join(engine, 'workbench', 'build.py')
|
|
56
|
+
if (!existsSync(script)) {
|
|
57
|
+
return { ok: false, code: 'WORKBENCH_MISSING', message: 'workbench/build.py is missing. Re-run setup --apply.' }
|
|
58
|
+
}
|
|
59
|
+
const result = spawnSync(python, [script], { encoding: 'utf8', cwd: engine })
|
|
60
|
+
if (result.status !== 0) {
|
|
61
|
+
return { ok: false, code: 'WORKBENCH_BUILD_FAILED', message: result.stderr.trim() || result.stdout.trim() || 'workbench build failed' }
|
|
62
|
+
}
|
|
63
|
+
return { ok: true, message: result.stdout.trim() }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function startWorkbench(engine, { noOpen = false } = {}) {
|
|
67
|
+
const python = python3Bin()
|
|
68
|
+
if (!python) {
|
|
69
|
+
return { ok: false, code: 'PYTHON3_REQUIRED', message: 'python3 is required for the workbench. Install Python 3 and retry.' }
|
|
70
|
+
}
|
|
71
|
+
ensureWorkbench(engine)
|
|
72
|
+
const built = buildWorkbench(engine)
|
|
73
|
+
if (!built.ok) return built
|
|
74
|
+
const url = WORKBENCH_URL
|
|
75
|
+
if (await portInUse()) {
|
|
76
|
+
if (!noOpen && process.env.EASYSOCIABLE_NO_OPEN !== 'true') openUrl(url)
|
|
77
|
+
return { ok: true, alreadyRunning: true, url, build: built.message }
|
|
78
|
+
}
|
|
79
|
+
const args = [join(engine, 'workbench', 'serve.py')]
|
|
80
|
+
if (noOpen) args.push('--no-open')
|
|
81
|
+
const child = spawn(python, args, { cwd: engine, detached: true, stdio: 'ignore' })
|
|
82
|
+
child.unref()
|
|
83
|
+
return { ok: true, alreadyRunning: false, url, pid: child.pid, build: built.message }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function openUrl(url) {
|
|
87
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open'
|
|
88
|
+
const args = process.platform === 'win32' ? ['/c', 'start', url] : [url]
|
|
89
|
+
spawn(command, args, { stdio: 'ignore', detached: true }).unref()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseArgs(argv) {
|
|
93
|
+
const options = { engine: '', command: '', noOpen: false, json: false }
|
|
94
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
95
|
+
const arg = argv[i]
|
|
96
|
+
if (arg === '--engine') options.engine = argv[++i] ?? ''
|
|
97
|
+
else if (arg === '--no-open') options.noOpen = true
|
|
98
|
+
else if (arg === '--json') options.json = true
|
|
99
|
+
else if (arg === '--help' || arg === '-h') options.help = true
|
|
100
|
+
else if (!arg.startsWith('--') && !options.command) options.command = arg
|
|
101
|
+
else throw new Error(`Unknown argument: ${arg}`)
|
|
102
|
+
}
|
|
103
|
+
return options
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function main() {
|
|
107
|
+
const options = parseArgs(process.argv.slice(2))
|
|
108
|
+
if (options.help) {
|
|
109
|
+
output({ usage: 'node workbench.mjs build|start --engine <Content Engine> [--no-open] [--json]' }, false)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
if (!options.engine) throw new Error('--engine is required')
|
|
113
|
+
const engine = resolve(options.engine)
|
|
114
|
+
if (options.command === 'build') {
|
|
115
|
+
ensureWorkbench(engine)
|
|
116
|
+
const built = buildWorkbench(engine)
|
|
117
|
+
output(built, options.json)
|
|
118
|
+
if (!built.ok) process.exitCode = 2
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if (options.command === 'start' || options.command === '') {
|
|
122
|
+
const started = await startWorkbench(engine, { noOpen: options.noOpen })
|
|
123
|
+
output(started, options.json)
|
|
124
|
+
if (!started.ok) process.exitCode = 2
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
throw new Error('Command must be build or start')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
131
|
+
main().catch((error) => {
|
|
132
|
+
output({ code: 'WORKBENCH_FAILED', message: error instanceof Error ? error.message : String(error) }, true)
|
|
133
|
+
process.exitCode = 1
|
|
134
|
+
})
|
|
135
|
+
}
|