@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
|
@@ -34,7 +34,7 @@ easysociable templates publish --id <draftId> --json
|
|
|
34
34
|
Or two steps:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
easysociable templates update --id <draftId> --file ./draft-patch.json --json
|
|
37
|
+
easysociable templates update-draft --id <draftId> --file ./draft-patch.json --json
|
|
38
38
|
easysociable templates publish --id <draftId> --json
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -15,6 +15,15 @@ Run after draft creation and before the user handoff. Source inspection alone is
|
|
|
15
15
|
|
|
16
16
|
0. If `theme-tokens.json` exists, compare screenshot contrast to its `contrast` report for the active mode. Failed pairs are hard failures or cap `colorContrast` at 5. When `surfacePreview.kind` is `gradient` or `image`, also check text against `contrastAnchor` / scrim — do not assume Theme.canvas alone matches the wash.
|
|
17
17
|
|
|
18
|
+
0b. **Reference compare (required when `source.kind = image_reference`)** — before numeric scoring:
|
|
19
|
+
- Open the **reference image** and the **Studio canvas screenshot** side by side
|
|
20
|
+
- Check against `design-plan.json` → `referenceSpec`: margins, card stack order, radii, gaps, type hierarchy, badge/icon box sizes
|
|
21
|
+
- Confirm every `referenceImageInventory` entry is an **image-slot frame** on canvas (not CSS fake faces/illustrations). Missing inventory → hard failure
|
|
22
|
+
- Confirm recognizable glyphs used Icon Catalog (or documented degrade), not hand-drawn substitutes
|
|
23
|
+
- A draft that is “structurally educational” but clearly a different layout/density than the reference **fails** this pass even if the numeric rubric might scrape by
|
|
24
|
+
- Record on `visual-qa.json`: `sourceKind: "image_reference"` and `referenceCompare: { performed, geometryMatch, imageSlotsMatchInventory, notes? }`. A `pass` verdict requires all three booleans `true` (`validate-design-v2.mjs` enforces)
|
|
25
|
+
- If browser/screenshot tools are unavailable, mark reference compare **pending** (`performed: false`) and do not claim Visual QA passed
|
|
26
|
+
|
|
18
27
|
1. **Craft Pass (philosophy discipline)** — before numeric scoring:
|
|
19
28
|
- Read `design-plan.json` → `philosophy.craftBar` and the selected concept’s `philosophyExpression`
|
|
20
29
|
- If the screenshot contradicts the philosophy (e.g. generic SaaS three-card grid vs “monumental single metric”), treat that as a composition/hierarchy issue and plan a revise toward the stated expression
|
|
@@ -32,6 +41,7 @@ Run after draft creation and before the user handoff. Source inspection alone is
|
|
|
32
41
|
- missing required slot content/image
|
|
33
42
|
- background or decoration obscuring the focal message
|
|
34
43
|
- third-party temp URLs or broken CDN for image surface/slots
|
|
44
|
+
- **reference path:** pictorial regions replaced by CSS/emoji stand-ins; composition clearly diverged from the reference without user acceptance
|
|
35
45
|
3. Score the fixed rubric in `references/DESIGN_V2_1.md`:
|
|
36
46
|
|
|
37
47
|
- hierarchy 20
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Content Skill filesystem reference
|
|
2
|
-
|
|
3
|
-
The Content Skill does not own a Content CLI in V1. It writes the user's
|
|
4
|
-
existing Obsidian Vault directly and uses the unified `easysociable` command only
|
|
5
|
-
for the hosted Template/Slideshow handoff.
|
|
6
|
-
|
|
7
|
-
## Local workflow
|
|
8
|
-
|
|
9
|
-
Use the host's filesystem tools to detect Obsidian, ask for 中文 or English,
|
|
10
|
-
choose a Vault, preview the locale engine paths (`Content Engine/` or
|
|
11
|
-
`内容引擎/`) with `--locale zh|en`, create or validate Markdown/JSON files after
|
|
12
|
-
confirmation, and open the Vault in Obsidian.
|
|
13
|
-
|
|
14
|
-
Do not invent `easysociable-content init/start/doctor` commands and do not pass
|
|
15
|
-
`--data-dir`. Those belong to the retired Content Runtime.
|
|
16
|
-
|
|
17
|
-
## Hosted handoff
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
easysociable templates list --platforms tiktok --json
|
|
21
|
-
easysociable slideshows create --file ./runs/<slug>/pack-tiktok.json --json
|
|
22
|
-
easysociable slideshows render --id run_xxx --idempotency-key run_xxx-final-1 --json
|
|
23
|
-
easysociable renders wait --id job_xxx --json
|
|
24
|
-
easysociable renders export --id job_xxx --output ./exports/<slug>/tiktok --json
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Use the unified MCP first when the host exposes it. The CLI is the fallback.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Unified MCP reference
|
|
2
|
-
|
|
3
|
-
V1 has one MCP server installed by the unified EasySociable installer. It owns
|
|
4
|
-
hosted Template, Image, Profile, Theme, Slideshow and Render tools. The Content
|
|
5
|
-
Skill itself does not require a Content MCP or local MCP bridge.
|
|
6
|
-
|
|
7
|
-
Use the MCP tool registry exposed by `easysociable mcp serve --transport stdio`. Content work stays
|
|
8
|
-
in the Obsidian filesystem; hosted calls use the same Gateway client and account
|
|
9
|
-
as the CLI.
|
|
10
|
-
|
|
11
|
-
## Handoff sequence
|
|
12
|
-
|
|
13
|
-
1. Write a `content-pack.v1` into the local Run folder.
|
|
14
|
-
2. Call `create_slideshow_run` with the Pack.
|
|
15
|
-
3. Open the returned Studio URL and wait for Save.
|
|
16
|
-
4. After confirmation call `render_slideshow_run`.
|
|
17
|
-
5. Poll the render job and export the PNG set into the Vault.
|
|
18
|
-
|
|
19
|
-
The MCP server must never expose private AST, Binding Maps, RenderPlans, R2
|
|
20
|
-
credentials, Directus credentials, Stripe credentials or Local Runtime tokens.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# PagePack handoff
|
|
2
|
-
|
|
3
|
-
`content-pack.v1` is the boundary between semantic content and visual
|
|
4
|
-
composition. It contains platform, optional title, and ordered pages with a
|
|
5
|
-
semantic `role` and public `slots`. The Slideshow Skill performs template
|
|
6
|
-
selection and rendering; Content does not call Template APIs.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Troubleshooting
|
|
2
|
-
|
|
3
|
-
The V1 Content Skill has no Content CLI, Local API, SQLite runtime or runtime
|
|
4
|
-
token. If setup fails, stop before writing files, show the Obsidian install and
|
|
5
|
-
Vault-selection guidance, and ask the user to retry after the prerequisite is
|
|
6
|
-
ready. Never invent `--data-dir`, `init`, `doctor` or runtime credentials.
|
|
7
|
-
|
|
8
|
-
If a local Run is incomplete, keep the existing Markdown/JSON intact and report
|
|
9
|
-
the missing file or field. Do not auto-migrate or repair an existing Content
|
|
10
|
-
Engine folder. Publication, metrics, retrospectives and experiments are
|
|
11
|
-
deferred in V1; keep those notes as ordinary Markdown only.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Vault layout
|
|
2
|
-
|
|
3
|
-
Setup asks the user to choose a locale, then creates one engine root:
|
|
4
|
-
|
|
5
|
-
| Locale | Root | Runs | Exports |
|
|
6
|
-
|---|---|---|---|
|
|
7
|
-
| `en` | `Content Engine/` | `runs/<slug>/` | `exports/<slug>/<platform>/` |
|
|
8
|
-
| `zh` | `内容引擎/` | `成稿/<slug>/` | `导出/<slug>/<platform>/` |
|
|
9
|
-
|
|
10
|
-
Read `locale.json` in that root after setup:
|
|
11
|
-
|
|
12
|
-
```json
|
|
13
|
-
{ "locale": "zh" }
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
English folders stay English (`accounts`, `inbox`, `swipe`, `topics`, …).
|
|
17
|
-
Chinese folders are `账号`, `收件箱`, `拆解`, `选题`, `素材`, `档案`, `已发布`,
|
|
18
|
-
`百科`, `平台`. Platform note filenames stay `x.md`, `threads.md`, `tiktok.md`.
|
|
19
|
-
|
|
20
|
-
Never create both roots in one Vault. Pack JSON keys stay English.
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'
|
|
4
|
-
import { homedir } from 'node:os'
|
|
5
|
-
import { join, resolve } from 'node:path'
|
|
6
|
-
import { spawnSync } from 'node:child_process'
|
|
7
|
-
import { ROOT, filesFor } from './starter.mjs'
|
|
8
|
-
|
|
9
|
-
const LOCALES = new Set(['en', 'zh'])
|
|
10
|
-
|
|
11
|
-
function obsidianCandidates() {
|
|
12
|
-
if (process.env.EASYSOCIABLE_OBSIDIAN_APP)
|
|
13
|
-
return [process.env.EASYSOCIABLE_OBSIDIAN_APP]
|
|
14
|
-
return [
|
|
15
|
-
'/Applications/Obsidian.app',
|
|
16
|
-
join(homedir(), 'Applications/Obsidian.app'),
|
|
17
|
-
'/System/Applications/Obsidian.app',
|
|
18
|
-
].filter(Boolean)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function hasObsidian() {
|
|
22
|
-
if (process.env.EASYSOCIABLE_SKIP_OBSIDIAN_CHECK === 'true') return true
|
|
23
|
-
return obsidianCandidates().some((path) => existsSync(path))
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function parseArgs(argv) {
|
|
27
|
-
const options = { vault: '', locale: '', apply: false, validate: false, preview: false, noOpen: false, json: false }
|
|
28
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
29
|
-
const arg = argv[i]
|
|
30
|
-
if (arg === '--vault') options.vault = argv[++i] ?? ''
|
|
31
|
-
else if (arg === '--locale') options.locale = argv[++i] ?? ''
|
|
32
|
-
else if (arg === '--apply') options.apply = true
|
|
33
|
-
else if (arg === '--validate') options.validate = true
|
|
34
|
-
else if (arg === '--preview') options.preview = true
|
|
35
|
-
else if (arg === '--no-open') options.noOpen = true
|
|
36
|
-
else if (arg === '--json') options.json = true
|
|
37
|
-
else if (arg === '--help' || arg === '-h') options.help = true
|
|
38
|
-
else throw new Error(`Unknown argument: ${arg}`)
|
|
39
|
-
}
|
|
40
|
-
return options
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function output(value, json) {
|
|
44
|
-
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : `${value.message ?? value}\n`)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function isDir(path) {
|
|
48
|
-
return existsSync(path) && statSync(path).isDirectory()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function enginePaths(vault) {
|
|
52
|
-
return { en: join(vault, ROOT.en), zh: join(vault, ROOT.zh) }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function detectedLocale(vault) {
|
|
56
|
-
const paths = enginePaths(vault)
|
|
57
|
-
const en = isDir(paths.en)
|
|
58
|
-
const zh = isDir(paths.zh)
|
|
59
|
-
if (en && zh) return { code: 'BOTH_ENGINES', message: 'Vault has both Content Engine/ and 内容引擎/. Choose one folder before setup.' }
|
|
60
|
-
if (zh) return { locale: 'zh' }
|
|
61
|
-
if (en) return { locale: 'en' }
|
|
62
|
-
return { locale: '' }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function resolveLocale(vault, requested) {
|
|
66
|
-
if (requested && !LOCALES.has(requested)) {
|
|
67
|
-
return { code: 'LOCALE_INVALID', message: 'Choose --locale en or --locale zh.' }
|
|
68
|
-
}
|
|
69
|
-
const found = detectedLocale(vault)
|
|
70
|
-
if (found.code) return found
|
|
71
|
-
if (requested && found.locale && requested !== found.locale) {
|
|
72
|
-
return { code: 'LOCALE_MISMATCH', message: `Vault already uses locale ${found.locale}. Do not create the other language tree.` }
|
|
73
|
-
}
|
|
74
|
-
const locale = requested || found.locale
|
|
75
|
-
if (!locale) return { code: 'LOCALE_REQUIRED', message: 'Ask the user to choose 中文 (zh) or English (en), then pass --locale.' }
|
|
76
|
-
return { locale }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function inventory(root, locale) {
|
|
80
|
-
const files = filesFor(locale)
|
|
81
|
-
const engine = join(root, ROOT[locale])
|
|
82
|
-
const missing = []
|
|
83
|
-
const existing = []
|
|
84
|
-
for (const relative of Object.keys(files)) {
|
|
85
|
-
const path = join(engine, relative)
|
|
86
|
-
;(existsSync(path) ? existing : missing).push(relative)
|
|
87
|
-
}
|
|
88
|
-
return { engine, locale, rootName: ROOT[locale], missing, existing }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function validateVault(vault, requestedLocale) {
|
|
92
|
-
const absolute = resolve(vault)
|
|
93
|
-
const errors = []
|
|
94
|
-
if (!existsSync(absolute) || !statSync(absolute).isDirectory()) errors.push('Vault path does not exist or is not a directory')
|
|
95
|
-
const resolved = errors.length ? { locale: requestedLocale || 'en' } : resolveLocale(absolute, requestedLocale)
|
|
96
|
-
if (resolved.code) return { vault: absolute, valid: false, errors: [resolved.message], ...resolved, created: [] }
|
|
97
|
-
const result = inventory(absolute, resolved.locale)
|
|
98
|
-
if (existsSync(result.engine) && !isDir(result.engine)) errors.push(`${result.rootName} exists but is not a directory`)
|
|
99
|
-
return { vault: absolute, ...result, valid: errors.length === 0, errors }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function applyStarter(vault, locale, noOpen) {
|
|
103
|
-
const report = validateVault(vault, locale)
|
|
104
|
-
if (!report.valid) return report
|
|
105
|
-
const files = filesFor(report.locale)
|
|
106
|
-
mkdirSync(report.engine, { recursive: true })
|
|
107
|
-
for (const relative of report.missing) {
|
|
108
|
-
const path = join(report.engine, relative)
|
|
109
|
-
mkdirSync(join(path, '..'), { recursive: true })
|
|
110
|
-
writeFileSync(path, files[relative], { encoding: 'utf8', flag: 'wx' })
|
|
111
|
-
}
|
|
112
|
-
if (!noOpen && process.env.EASYSOCIABLE_NO_OPEN !== 'true') {
|
|
113
|
-
const vaultName = report.vault.split('/').pop() ?? 'Vault'
|
|
114
|
-
spawnSync('open', [`obsidian://open?vault=${encodeURIComponent(vaultName)}`], { stdio: 'ignore' })
|
|
115
|
-
}
|
|
116
|
-
return { ...validateVault(vault, report.locale), created: [...report.missing] }
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function printHelp() {
|
|
120
|
-
process.stdout.write(`Usage: node setup.mjs --vault <existing-vault> --locale en|zh [--preview|--apply|--validate] [--no-open] [--json]\n\nObsidian must be installed. Ask the user to choose English or 中文 before apply. Existing files are never overwritten.\n`)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const options = parseArgs(process.argv.slice(2))
|
|
125
|
-
if (options.help) {
|
|
126
|
-
printHelp()
|
|
127
|
-
process.exit(0)
|
|
128
|
-
}
|
|
129
|
-
if (!hasObsidian()) {
|
|
130
|
-
const error = { code: 'OBSIDIAN_REQUIRED', message: 'Obsidian.app is required. Install Obsidian from https://obsidian.md/download before setup.', created: false }
|
|
131
|
-
output(error, options.json)
|
|
132
|
-
process.exitCode = 2
|
|
133
|
-
}
|
|
134
|
-
else if (!options.vault) {
|
|
135
|
-
output({ code: 'VAULT_REQUIRED', message: 'Choose an existing Obsidian Vault with --vault.', created: false }, options.json)
|
|
136
|
-
process.exitCode = 2
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
const report = validateVault(options.vault, options.locale)
|
|
140
|
-
if (!report.valid) {
|
|
141
|
-
output({ ...report, created: [] }, options.json)
|
|
142
|
-
process.exitCode = 2
|
|
143
|
-
}
|
|
144
|
-
else if (options.apply) {
|
|
145
|
-
const applied = applyStarter(options.vault, report.locale, options.noOpen)
|
|
146
|
-
output({ ...applied, action: 'applied' }, options.json)
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
output({ ...report, action: options.validate ? 'validated' : 'preview', created: [] }, options.json)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
output({ code: 'CONTENT_SETUP_FAILED', message: error instanceof Error ? error.message : String(error), created: false }, true)
|
|
155
|
-
process.exitCode = 1
|
|
156
|
-
}
|
|
@@ -1,71 +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 vault = join(fixture, 'Vault')
|
|
10
|
-
mkdirSync(vault)
|
|
11
|
-
const env = { ...process.env, EASYSOCIABLE_SKIP_OBSIDIAN_CHECK: 'true', 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 missingObsidian = spawnSync(process.execPath, [script, '--vault', vault, '--locale', 'en', '--apply', '--json'], {
|
|
19
|
-
encoding: 'utf8',
|
|
20
|
-
env: { ...env, EASYSOCIABLE_SKIP_OBSIDIAN_CHECK: 'false', EASYSOCIABLE_OBSIDIAN_APP: join(fixture, 'missing-Obsidian.app') },
|
|
21
|
-
})
|
|
22
|
-
assert.equal(missingObsidian.status, 2)
|
|
23
|
-
assert.equal(existsSync(join(vault, 'Content Engine')), false)
|
|
24
|
-
|
|
25
|
-
const missingLocale = run(['--vault', vault, '--preview'])
|
|
26
|
-
assert.equal(missingLocale.status, 2)
|
|
27
|
-
assert.equal(JSON.parse(missingLocale.stdout).code, 'LOCALE_REQUIRED')
|
|
28
|
-
|
|
29
|
-
const preview = run(['--vault', vault, '--locale', 'en', '--preview'])
|
|
30
|
-
assert.equal(preview.status, 0)
|
|
31
|
-
const previewReport = JSON.parse(preview.stdout)
|
|
32
|
-
assert.equal(previewReport.created.length, 0)
|
|
33
|
-
assert.equal(previewReport.valid, true)
|
|
34
|
-
assert.equal(previewReport.locale, 'en')
|
|
35
|
-
|
|
36
|
-
const apply = run(['--vault', vault, '--locale', 'en', '--apply', '--no-open'])
|
|
37
|
-
assert.equal(apply.status, 0)
|
|
38
|
-
const applied = JSON.parse(apply.stdout)
|
|
39
|
-
assert.equal(applied.action, 'applied')
|
|
40
|
-
assert.equal(applied.created.length > 5, true)
|
|
41
|
-
assert.equal(readFileSync(join(vault, 'Content Engine', 'CLAUDE.md'), 'utf8').includes('local Obsidian'), true)
|
|
42
|
-
assert.equal(JSON.parse(readFileSync(join(vault, 'Content Engine', 'locale.json'), 'utf8')).locale, 'en')
|
|
43
|
-
|
|
44
|
-
const sentinel = join(vault, 'Content Engine', 'runs', 'user-note.md')
|
|
45
|
-
writeFileSync(sentinel, 'keep me', 'utf8')
|
|
46
|
-
const second = run(['--vault', vault, '--apply', '--no-open'])
|
|
47
|
-
assert.equal(second.status, 0)
|
|
48
|
-
assert.equal(readFileSync(sentinel, 'utf8'), 'keep me')
|
|
49
|
-
|
|
50
|
-
const mismatch = run(['--vault', vault, '--locale', 'zh', '--apply', '--no-open'])
|
|
51
|
-
assert.equal(mismatch.status, 2)
|
|
52
|
-
assert.equal(JSON.parse(mismatch.stdout).code, 'LOCALE_MISMATCH')
|
|
53
|
-
assert.equal(existsSync(join(vault, '内容引擎')), false)
|
|
54
|
-
|
|
55
|
-
const zhVault = join(fixture, 'ZhVault')
|
|
56
|
-
mkdirSync(zhVault)
|
|
57
|
-
const zhApply = run(['--vault', zhVault, '--locale', 'zh', '--apply', '--no-open'])
|
|
58
|
-
assert.equal(zhApply.status, 0)
|
|
59
|
-
const zhEngine = join(zhVault, '内容引擎')
|
|
60
|
-
assert.equal(existsSync(join(vault, 'Content Engine')), true)
|
|
61
|
-
assert.equal(readFileSync(join(zhEngine, 'CLAUDE.md'), 'utf8').includes('内容引擎'), true)
|
|
62
|
-
assert.equal(existsSync(join(zhEngine, '成稿', '_索引.md')), true)
|
|
63
|
-
assert.equal(existsSync(join(zhEngine, '选题', '观察清单.md')), true)
|
|
64
|
-
assert.equal(JSON.parse(readFileSync(join(zhEngine, 'locale.json'), 'utf8')).locale, 'zh')
|
|
65
|
-
assert.equal(existsSync(join(zhVault, 'Content Engine')), false)
|
|
66
|
-
|
|
67
|
-
console.log('Content Skill setup smoke passed.')
|
|
68
|
-
}
|
|
69
|
-
finally {
|
|
70
|
-
rmSync(fixture, { recursive: true, force: true })
|
|
71
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export const ROOT = { en: 'Content Engine', zh: '内容引擎' }
|
|
2
|
-
|
|
3
|
-
export const STARTER = {
|
|
4
|
-
en: {
|
|
5
|
-
'CLAUDE.md': `# Content Engine\n\nThis folder is the local Obsidian source of truth for EasySociable content runs.\n\n- Keep semantic copy and media references here.\n- Save each run under \`runs/<slug>/\`.\n- Save final PNGs and manifest files only under \`exports/<slug>/<platform>/\`.\n- Do not put private Template AST, Binding Maps or RenderPlans in this vault.\n- Use the unified \`easysociable\` CLI/MCP for hosted Template and Slideshow operations.\n- Pack JSON (\`content-pack.v1\`) field names stay English for the Slideshow handoff.\n`,
|
|
6
|
-
'accounts/_index.md': '# Accounts\n\nAdd account notes here. Do not store API keys or passwords.\n',
|
|
7
|
-
'exports/README.md': '# Exports\n\nFinal PNGs and manifests written by the unified render export command.\n',
|
|
8
|
-
'inbox/README.md': '# Inbox\n\nTemporary notes awaiting triage into topics, swipe files or runs.\n',
|
|
9
|
-
'inbox/_index.md': '# Inbox Index\n',
|
|
10
|
-
'profiles/_index.md': '# Profiles\n\nStore local profile references and presentation preferences.\n',
|
|
11
|
-
'published/_index.md': '# Published\n\nOptional local index of content already published elsewhere.\n',
|
|
12
|
-
'raw/_index.md': '# Raw\n\nSource notes and imported research.\n',
|
|
13
|
-
'runs/README.md': '# Runs\n\nEach run stores semantic content, platform Packs and render references.\n',
|
|
14
|
-
'runs/_index.md': '# Runs Index\n',
|
|
15
|
-
'swipe/_index.md': '# Swipe\n',
|
|
16
|
-
'swipe/_template.md': '# Swipe Item\n\n## Source\n\n## Pattern\n\n## Reusable insight\n',
|
|
17
|
-
'topics/_index.md': '# Topics\n',
|
|
18
|
-
'topics/WATCHLIST.md': '# Watchlist\n\nKeep links and observations here. Network tools are optional.\n',
|
|
19
|
-
'topics/_template-item.md': '# Topic\n\n## Thesis\n\n## Audience\n\n## Evidence\n',
|
|
20
|
-
'wiki/README.md': '# Wiki\n\nStable concepts and operating rules.\n',
|
|
21
|
-
'platforms/x.md': '# X\n\nKeep X-specific copy and constraints here.\n',
|
|
22
|
-
'platforms/threads.md': '# Threads\n\nKeep Threads-specific copy and constraints here.\n',
|
|
23
|
-
'platforms/tiktok.md': '# TikTok\n\nKeep TikTok-specific copy and constraints here.\n',
|
|
24
|
-
},
|
|
25
|
-
zh: {
|
|
26
|
-
'CLAUDE.md': `# 内容引擎\n\n此文件夹是 EasySociable 内容成稿的本地 Obsidian 真相源。\n\n- 在此保存语义文案和素材引用。\n- 每条成稿放在 \`成稿/<slug>/\`。\n- 最终 PNG 和清单只放在 \`导出/<slug>/<platform>/\`。\n- 不要把模板私有 AST、Binding Map 或 RenderPlan 放进此库。\n- 托管的模板与幻灯片操作使用统一的 \`easysociable\` CLI/MCP。\n- Pack JSON(\`content-pack.v1\`)的字段名保持英文,以便交给 Slideshow Skill。\n`,
|
|
27
|
-
'账号/_索引.md': '# 账号\n\n在此记录账号备注。不要存放 API 密钥或密码。\n',
|
|
28
|
-
'导出/说明.md': '# 导出\n\n由统一渲染导出命令写入的最终 PNG 和清单文件。\n',
|
|
29
|
-
'收件箱/说明.md': '# 收件箱\n\n待整理到选题、拆解或成稿的临时笔记。\n',
|
|
30
|
-
'收件箱/_索引.md': '# 收件箱索引\n',
|
|
31
|
-
'档案/_索引.md': '# 档案\n\n存放本地档案引用和呈现偏好。\n',
|
|
32
|
-
'已发布/_索引.md': '# 已发布\n\n可选:已在其他地方发布的内容本地索引。\n',
|
|
33
|
-
'素材/_索引.md': '# 素材\n\n源笔记和导入的调研。\n',
|
|
34
|
-
'成稿/说明.md': '# 成稿\n\n每条成稿保存语义内容、各平台 Pack 和渲染引用。\n',
|
|
35
|
-
'成稿/_索引.md': '# 成稿索引\n',
|
|
36
|
-
'拆解/_索引.md': '# 拆解\n',
|
|
37
|
-
'拆解/_模板.md': '# 拆解条目\n\n## 来源\n\n## 模式\n\n## 可复用洞察\n',
|
|
38
|
-
'选题/_索引.md': '# 选题\n',
|
|
39
|
-
'选题/观察清单.md': '# 观察清单\n\n在此保存链接和观察。网络检索工具是可选的。\n',
|
|
40
|
-
'选题/_模板.md': '# 选题\n\n## 论点\n\n## 受众\n\n## 证据\n',
|
|
41
|
-
'百科/说明.md': '# 百科\n\n稳定概念和操作规则。\n',
|
|
42
|
-
'平台/x.md': '# X\n\n在此保存 X 平台的文案和约束。\n',
|
|
43
|
-
'平台/threads.md': '# Threads\n\n在此保存 Threads 平台的文案和约束。\n',
|
|
44
|
-
'平台/tiktok.md': '# TikTok\n\n在此保存 TikTok 平台的文案和约束。\n',
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function filesFor(locale) {
|
|
49
|
-
return {
|
|
50
|
-
...STARTER[locale],
|
|
51
|
-
'locale.json': `${JSON.stringify({ locale }, null, 2)}\n`,
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Local analysis notes
|
|
2
|
-
|
|
3
|
-
Save source notes and evidence as Markdown under the selected Obsidian Run.
|
|
4
|
-
Record missing context as a checklist in the Run; do not call a Content CLI,
|
|
5
|
-
Local API or hosted analysis backend.
|
|
6
|
-
|
|
7
|
-
Template and Slideshow Skills consume the resulting local Pack only after the
|
|
8
|
-
Agent and user agree on the content.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Pack and export workflow
|
|
2
|
-
|
|
3
|
-
The Content Skill writes a public `content-pack.v1` handoff. It does not choose
|
|
4
|
-
private template internals and does not render images itself.
|
|
5
|
-
|
|
6
|
-
```text
|
|
7
|
-
Obsidian Run
|
|
8
|
-
→ platform Pack
|
|
9
|
-
→ unified Template/Slideshow MCP or CLI
|
|
10
|
-
→ Studio preview
|
|
11
|
-
→ user confirmation
|
|
12
|
-
→ final PNG render
|
|
13
|
-
→ renders export
|
|
14
|
-
→ locale exports/<slug>/<platform>/01.png + manifest.json
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
For a single image, call the published Page Template flow. For multiple pages,
|
|
18
|
-
call the Slideshow Run flow. Preview never consumes Render Page allowance;
|
|
19
|
-
final rendering does.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# Deferred in V1
|
|
2
|
-
|
|
3
|
-
Experiments, hypotheses, metrics and proposal activation are retained only as
|
|
4
|
-
historical documentation. The V1 Content Skill does not expose these commands
|
|
5
|
-
or a backend. Write any future notes as ordinary Markdown in the Obsidian
|
|
6
|
-
Vault; do not create hosted experiment records.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Obsidian-first workflow
|
|
2
|
-
|
|
3
|
-
There is no Content MCP fallback in V1. Use local filesystem tools to set up or
|
|
4
|
-
validate the existing Obsidian Vault, write the Run and platform Pack under
|
|
5
|
-
`Content Engine/runs/<slug>/` or `内容引擎/成稿/<slug>/`, then use the unified EasySociable MCP for hosted
|
|
6
|
-
Template/Slideshow operations. If the MCP is unavailable, use equivalent
|
|
7
|
-
`easysociable` CLI commands.
|
|
8
|
-
|
|
9
|
-
Never start a Local API, open SQLite or switch to the retired
|
|
10
|
-
`easysociable-content` binary.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Local working draft
|
|
2
|
-
|
|
3
|
-
V1 has no Working Draft CLI or Content MCP. Store the working draft as
|
|
4
|
-
Markdown/JSON inside the locale run folder (`Content Engine/runs/<slug>/` or
|
|
5
|
-
`内容引擎/成稿/<slug>/`), then
|
|
6
|
-
write the platform Pack before handing off to the Template or Slideshow Skill.
|
|
7
|
-
|
|
8
|
-
Never treat a local draft as a final render. The final checkpoint is the PNG
|
|
9
|
-
and `manifest.json` written by the unified `easysociable` CLI after the user
|
|
10
|
-
confirms the Studio preview.
|