@easysociable/cli 0.1.23 → 0.1.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easysociable/cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Agent-first CLI for EasySociable: Content Engine, Template, Slideshow, and MCP",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,8 +5,8 @@ description: >
5
5
  EasySociable Studio. Research facts, pick a proven hook and structure
6
6
  (Formula), lay pages out from reference designs (the user's, a Formula's,
7
7
  or the catalog's, shuffled), render-check every page locally, then create the Run and hand the user a Studio link.
8
- Ask the user only for what only they know (their Brand, once; their own
9
- images/screenshots). Final pixels come only from Studio Download.
8
+ Never block on a question: a missing Brand shows as a bound sample the user
9
+ fills once in Studio. Final pixels come only from Studio Download.
10
10
  ---
11
11
 
12
12
  # EasySociable Slideshow Skill
@@ -16,10 +16,11 @@ enough to post, where every word is still editable. The output is editable,
16
16
  so **a wrong guess is cheap and a question is expensive**: decide platform,
17
17
  page count, arc, style and copy yourself; the user fixes details in Studio.
18
18
 
19
- Ask only for what only the user knows:
19
+ What only the user has never blocks the Run; it becomes a visible, replaceable
20
+ stand-in they fill in Studio:
20
21
 
21
- - their Brand (IP name, handle, avatar) — once, ever;
22
- - their own material (screenshots, photos) when a page needs it.
22
+ - their Brand (name, handle, avatar) → bound samples (step 1);
23
+ - their own material (screenshots, photos) → image placeholders (step 5).
23
24
 
24
25
  ## Command output (`--json`)
25
26
 
@@ -49,22 +50,25 @@ Read results from these paths; don't guess:
49
50
  - `authenticated: true` → say once: "This Run will be created as
50
51
  `<account.email>` — open Studio with the same account."
51
52
 
52
- ### 1. Brand (ask once, ever)
53
+ ### 1. Brand (never ask)
53
54
 
54
55
  `easysociable brands list --json`.
55
56
 
56
57
  - A Brand exists → use it (the most recently updated if several), silently.
57
- - None → ask in **one** message, everything skippable:
58
- "What name and @handle should appear on your carousels? (Optional: an
59
- avatar/logo image path.) Reply 'skip' to post without a signature."
60
- Create it with `easysociable brands create --file brand.json`
61
- (`{ "name", "handle", "colors", "logoAssetId" }`) using the default palette
62
- that matches the deck's style (`references/REFERENCES.md`). An avatar: upload
63
- it first (`images upload --path <file> --json`) and pass `data[0].asset.id` as
64
- `logoAssetId`; `brands get` then returns its `logoUrl`.
65
- Skipped → no Brand: header shows the topic, the CTA has no handle, and that
66
- palette goes in `theme.json` (without a Brand or theme Studio falls back to
67
- its dark default).
58
+ Its colours and fonts style the Run (`--brand-id` in step 7).
59
+ - None, but the prompt gives a name / handle / avatar → create it:
60
+ `easysociable brands create --file brand.json`
61
+ (`{ "name", "handle", "colors", "logoAssetId" }`, colours = the deck style's
62
+ default palette in `references/REFERENCES.md`). An avatar: upload it first
63
+ (`images upload --path <file> --json`) and pass `data[0].asset.id` as
64
+ `logoAssetId`.
65
+ - None and nothing given → no Brand, no question. The style's palette goes in
66
+ `theme.json`.
67
+
68
+ Either way every Run carries the Brand spots (cover signature, CTA handle),
69
+ bound with `data-brand`; whatever the Brand lacks shows the sample `Your Name`
70
+ / `@yourhandle` / sample avatar (`references/REFERENCES.md` rule 5). Studio
71
+ asks the user for exactly those fields and updates every page when they fill them.
68
72
 
69
73
  ### 2. Facts and evidence
70
74
 
@@ -229,7 +233,11 @@ easysociable slideshows create --file pack.json --brand-id <brandId> --json
229
233
  # no Brand: --theme theme.json instead of --brand-id
230
234
  ```
231
235
 
232
- Reply once with: the `studioUrl`, the arc in one line, the sources (facts and
236
+ Reply once with: the `studioUrl`, the arc in one line, any Brand sample
237
+ left ("The name, handle and avatar on pages 1 and 6 are samples: fill them
238
+ once in the bar at the top of Studio, or in Brands at
239
+ https://my.easysociable.com/brands, and every page updates"; name only the
240
+ fields that are samples), the sources (facts and
233
241
  each evidence image: "p1 image: <url>"), and each image placeholder to replace (page + what to upload). Tell them to edit in
234
242
  Studio and click **Download** for the PNG ZIP.
235
243
 
@@ -36,13 +36,23 @@ Roles overlap in the catalog, so pass related ones together: `hook,cover` ·
36
36
  `easysociable icons search --query <word>` on a tinted block. Never a grey placeholder or an
37
37
  empty box on the cover. If nothing fits, delete the area and give the
38
38
  headline its space.
39
- 5. Brand chrome: the header label is the Brand name (or the topic when there is
40
- no Brand); the CTA names the Brand handle. Never leave a reference's original
41
- name or handle (e.g. "Will.AI.m") on a page. **Bind them** so a Brand change
42
- updates every page: the element holding only the Brand name gets
43
- `data-brand="name"`, only the handle `data-brand="handle"`, and an avatar /
44
- logo `<img>` gets `data-brand="avatar"` with the Brand's `logoUrl` as `src`.
45
- Without a Brand, leave them unbound (the topic is not the Brand).
39
+ 5. Brand chrome — every Run carries the Brand, bound so one Brand edit updates
40
+ every page. The cover has a signature (avatar + name + handle); the CTA has
41
+ avatar + handle next to the action. Inner pages keep only the Brand spots
42
+ their reference already has (never add chrome by habit). Never leave a
43
+ reference's original name or handle (e.g. "Will.AI.m") on a page.
44
+ - The element holding only the name gets `data-brand="name"`, only the
45
+ handle `data-brand="handle"`, the avatar / logo `<img>` `data-brand="avatar"`.
46
+ - Value: the Brand's `name`, `@handle`, `logoUrl`. A field the Brand lacks
47
+ (or no Brand at all) → the sample: `Your Name`, `@yourhandle`, and the
48
+ sample avatar below, still bound. Studio swaps each sample the moment the
49
+ user fills that field, and asks for it until then.
50
+ - Size the spots for a real name (up to ~22 characters) — `check-pages`
51
+ renders them with a long one.
52
+
53
+ Sample avatar (`objectFit: 'cover'`, round it with `borderRadius: '50%'`):
54
+ `data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxNjAnIGhlaWdodD0nMTYwJyB2aWV3Qm94PScwIDAgMTYwIDE2MCc+PHJlY3Qgd2lkdGg9JzE2MCcgaGVpZ2h0PScxNjAnIGZpbGw9JyNDOUNERDMnLz48Y2lyY2xlIGN4PSc4MCcgY3k9JzYyJyByPScyOCcgZmlsbD0nI0Y0RjVGNycvPjxwYXRoIGQ9J00yOCAxNjBjMi0zNCAyNC01MiA1Mi01MnM1MCAxOCA1MiA1MnonIGZpbGw9JyNGNEY1RjcnLz48L3N2Zz4=`
55
+
46
56
  6. Accent colour only on large text (≥ 36px, or ≥ 28px bold) and blocks. Small
47
57
  labels use the text or muted colour.
48
58
  7. Semantic `data-type` values (`title`, `subtitle`, `kicker`, `metric`,
@@ -24,6 +24,8 @@ const brandRaw = themeFile ? JSON.parse(readFileSync(themeFile, 'utf8')) : null
24
24
  const brand = brandRaw?.data ?? brandRaw
25
25
  const c = brand?.colors ?? brand?.color ?? {}
26
26
  // Studio default theme (apps/easysociable/app/lib/themeTokens.ts DEFAULT_AUTHORING_THEME_TOKENS).
27
+ // Brand spots render with the real Brand, or a long stand-in so a real name fits later.
28
+ const BRAND = { name: brand?.name || 'Alexandra Montgomery', handle: brand?.handle ? `@${brand.handle.replace(/^@/, '')}` : '@alexandra.montgomery', avatar: brand?.logoUrl || null }
27
29
  const colors = { canvas: c.canvas ?? '#0f1419', text: c.text ?? '#e8eef5', muted: c.textMuted ?? c.muted ?? '#8b9bb0', accent: c.accent ?? '#5b8cff', primary: c.primary ?? c.accent ?? '#5b8cff' }
28
30
 
29
31
  const chrome = [process.env.CHROME_PATH,
@@ -57,6 +59,7 @@ for (const [i, src] of SOURCES.entries()) {
57
59
  const box = document.createElement('div')
58
60
  box.setAttribute('data-authoring-theme', '')
59
61
  box.innerHTML = toHtml(src.json)
62
+ for (const el of box.querySelectorAll('[data-brand]')) { const f = el.dataset.brand; if (f === 'avatar') { if (BRAND.avatar) el.src = BRAND.avatar } else if (BRAND[f]) el.textContent = BRAND[f] }
60
63
  if (GRID) { box.style.cssText = 'width:' + src.width * S + 'px;height:' + src.height * S + 'px;overflow:hidden'; box.firstElementChild.style.transform = 'scale(' + S + ')'; box.firstElementChild.style.transformOrigin = '0 0' }
61
64
  stage.appendChild(box)
62
65
  }
@@ -112,7 +115,7 @@ for (const page of pack.pages) if (!page.layout?.studioJson) console.log(`page $
112
115
  const COLS = 4
113
116
  const html = `<!doctype html><meta charset="utf-8"><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=block">
114
117
  <style>${themeCss}</style><div id="stage"></div><pre id="report"></pre>
115
- <script>const SOURCES = ${JSON.stringify(pages).replace(/</g, '\\u003c')}, COLS = ${COLS};${pageScript}</script>`
118
+ <script>const SOURCES = ${JSON.stringify(pages).replace(/</g, '\\u003c')}, COLS = ${COLS}, BRAND = ${JSON.stringify(BRAND).replace(/</g, '\\u003c')};${pageScript}</script>`
116
119
  const file = join(outDir, 'pages.html')
117
120
  writeFileSync(file, html)
118
121
  const W = Math.max(...pages.map(p => p.width)), Hs = pages.map(p => p.height)
@@ -124,9 +127,13 @@ const rows = Math.ceil(pages.length / COLS)
124
127
  execFileSync(chrome, [...base, `--window-size=${Math.min(COLS, pages.length) * (W / 2 + 16) + 16},${rows * (Math.max(...Hs) / 2 + 16) + 16}`, `--screenshot=${join(outDir, 'grid.png')}`, pathToFileURL(file).href + '#grid'], { stdio: 'ignore' })
125
128
  const raw = dom.match(/<pre id="report">([\s\S]*?)<\/pre>/)?.[1]?.replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&')
126
129
  const reports = raw ? JSON.parse(raw) : pages.map(() => ({ errors: ['no report (page script did not finish)'], warnings: [] }))
130
+ // Every Run carries the Brand: a cover signature and a CTA handle (references/REFERENCES.md rule 5).
131
+ const brandSpots = { 0: ['name', 'handle', 'avatar'], [pages.length - 1]: ['handle', 'avatar'] }
127
132
  let failed = false
128
133
  for (const [i, page] of pages.entries()) {
129
134
  const report = reports[i]
135
+ for (const f of (brandSpots[i] ?? []).filter(f => !page.json.includes(`data-brand="${f}"`)))
136
+ report.errors.push(`no Brand ${f}: the ${i ? 'last' : 'first'} page needs a data-brand="${f}" element`)
130
137
  if (report.errors.length) failed = true
131
138
  console.log(`page ${page.n} (${page.role}): ${report.errors.length ? 'FAIL' : 'ok'}`)
132
139
  for (const e of report.errors) console.log(` ERROR ${e}`)