social-autoposter 1.6.185 → 1.6.186
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/mcp/dist/index.js +49 -13
- package/mcp/dist/setup.js +14 -0
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_menubar.py +7 -6
- package/mcp/package.json +1 -1
- package/package.json +1 -1
- package/scripts/snapshot.py +53 -17
- package/setup/SKILL.md +52 -7
package/mcp/dist/index.js
CHANGED
|
@@ -1092,11 +1092,13 @@ tool("engagement_mode", {
|
|
|
1092
1092
|
description: "Set or read the engagement LANES the autopilot drafts in. There are TWO independent lanes that " +
|
|
1093
1093
|
"can BOTH be on (the cycle then splits 50/50): PERSONAL BRAND (organic, link-free engagement in " +
|
|
1094
1094
|
"the user's own voice — ON by default) and PRODUCT PROMOTION (the marketing pipeline, link " +
|
|
1095
|
-
"replies — OFF by default, opt-in). This is a SETUP step: AFTER X is connected
|
|
1096
|
-
"is scanned
|
|
1095
|
+
"replies — OFF by default, opt-in). This is a SETUP step: AFTER X is connected, the profile " +
|
|
1096
|
+
"is scanned (for VOICE), and the user has answered the DICTATION interview (for TOPICS + corpus), " +
|
|
1097
|
+
"personal-brand is already the default, so ASK the user the ONE question: do they " +
|
|
1097
1098
|
"ALSO want to promote a product? Then call action:'set' with personal_brand:true and " +
|
|
1098
|
-
"promotion:true|false. Pass the voice/description
|
|
1099
|
-
"
|
|
1099
|
+
"promotion:true|false. Pass the voice/description you captured from the scan, the search_topics " +
|
|
1100
|
+
"you extracted PRIMARILY from the dictation, and the raw dictation transcript as content_corpus, " +
|
|
1101
|
+
"so the persona is grounded in who they actually are, AND expand those topics into a search_queries " +
|
|
1100
1102
|
"array of ~30 concrete X advanced-search strings in the SAME call (identical to project_config) " +
|
|
1101
1103
|
"so the personal-brand cycle has a real query bank on day one instead of running one crude " +
|
|
1102
1104
|
"topic-as-query. If they want promotion too, continue to configure the product project with " +
|
|
@@ -1130,15 +1132,22 @@ tool("engagement_mode", {
|
|
|
1130
1132
|
content_angle: z
|
|
1131
1133
|
.string()
|
|
1132
1134
|
.optional()
|
|
1133
|
-
.describe("Persona grounding: a paragraph of concrete first-hand experience the persona speaks from."),
|
|
1135
|
+
.describe("Persona grounding: a paragraph of concrete first-hand experience the persona speaks from, synthesized from the DICTATION interview (contrarian takes, earned expertise) with the scan as backup."),
|
|
1136
|
+
content_corpus: z
|
|
1137
|
+
.string()
|
|
1138
|
+
.optional()
|
|
1139
|
+
.describe("The RAW voice-memo transcript from the onboarding dictation interview, VERBATIM (do NOT " +
|
|
1140
|
+
"paraphrase or summarize). Persisted to the persona_corpus.txt sidecar (never config.json), " +
|
|
1141
|
+
"capped ~8000 chars. This is the grounding pool the drafter quotes real specifics from " +
|
|
1142
|
+
"(actual projects, numbers, opinions, phrasing), so keep it dense and first-hand."),
|
|
1134
1143
|
voice: z
|
|
1135
1144
|
.any()
|
|
1136
1145
|
.optional()
|
|
1137
|
-
.describe("Persona voice object {tone, never:[...]} captured from how they actually write."),
|
|
1146
|
+
.describe("Persona voice object {tone, never:[...]} captured from how they actually write (the profile scan) and calibrated by the dictation (who they like/hate reading, phrases they overuse, off-limits)."),
|
|
1138
1147
|
search_topics: z
|
|
1139
1148
|
.union([z.array(z.string()), z.string()])
|
|
1140
1149
|
.optional()
|
|
1141
|
-
.describe("~15 topics the persona has genuine experience with,
|
|
1150
|
+
.describe("~15 topics the persona has genuine experience with. Sourced PRIMARILY from the DICTATION interview (the 'subjects you could talk about for an hour' answer), with recurring themes from the profile scan as reinforcement. This is the ONLY field that changes what gets SCANNED on X, so it must reflect what the user WANTS to be in conversations about, not just what they already posted."),
|
|
1142
1151
|
search_queries: z
|
|
1143
1152
|
.union([z.array(z.string()), z.string()])
|
|
1144
1153
|
.optional()
|
|
@@ -1217,6 +1226,7 @@ tool("engagement_mode", {
|
|
|
1217
1226
|
content_angle: args.content_angle,
|
|
1218
1227
|
voice: args.voice,
|
|
1219
1228
|
search_topics: args.search_topics,
|
|
1229
|
+
content_corpus: args.content_corpus,
|
|
1220
1230
|
});
|
|
1221
1231
|
personaName = r.name;
|
|
1222
1232
|
personaCreated = r.created;
|
|
@@ -1556,17 +1566,43 @@ tool("project_config", {
|
|
|
1556
1566
|
grounding_instructions: scan.grounding_instructions,
|
|
1557
1567
|
website_research_instructions: WEBSITE_RESEARCH_INSTRUCTIONS,
|
|
1558
1568
|
onboarding: onboardingSnapshot(),
|
|
1559
|
-
next_step: "
|
|
1569
|
+
next_step: "FOUR steps, in order. FIRST (VOICE, from this scan): read the bio, posts, and comments " +
|
|
1560
1570
|
"as GROUND TRUTH and, per grounding_instructions, extract their profession/identity, " +
|
|
1561
1571
|
"voice & vibe (tone, phrasing, casing, tics), 2-4 verbatim golden-rule example replies, " +
|
|
1562
|
-
"a phrase bank + things they avoid, their icp
|
|
1563
|
-
"
|
|
1572
|
+
"a phrase bank + things they avoid, and their icp. The scan is BACKWARD-LOOKING (only what " +
|
|
1573
|
+
"they already posted) so it is the source for VOICE, not the primary source for topics. " +
|
|
1574
|
+
"SECOND (the DICTATION interview — this is where TOPICS + grounding corpus come from, do NOT " +
|
|
1575
|
+
"skip it and do NOT infer topics from the scan alone): tell the user to answer ALL of the " +
|
|
1576
|
+
"following in ONE spoken dictation (the Claude input box already supports dictation, so they " +
|
|
1577
|
+
"just talk once and you split the answers into fields). Ask verbatim, as a single numbered " +
|
|
1578
|
+
"list:\n" +
|
|
1579
|
+
" 1. Who are you, and what do you want to be known for? (-> description)\n" +
|
|
1580
|
+
" 2. What subjects could you talk about for an hour, work and non-work? (-> search_topics: " +
|
|
1581
|
+
"this is the LOAD-BEARING answer, it is the ONLY thing that decides what gets scanned on X, " +
|
|
1582
|
+
"so it must capture what they WANT to be in conversations about)\n" +
|
|
1583
|
+
" 3. Your most contrarian takes — what does everyone in your field get wrong, and what did " +
|
|
1584
|
+
"you used to believe that you have reversed on? (-> content_angle + corpus)\n" +
|
|
1585
|
+
" 4. What can you explain in 5 minutes that took you years, and what mistake do you watch " +
|
|
1586
|
+
"beginners make over and over? (-> content_angle + corpus)\n" +
|
|
1587
|
+
" 5. Best or worst thing that happened to you recently, and a failure you learned the most " +
|
|
1588
|
+
"from? (-> corpus, keeps drafts current)\n" +
|
|
1589
|
+
" 6. Who do you love or hate reading online, and any lines or phrases you say a lot? " +
|
|
1590
|
+
"(-> voice calibration)\n" +
|
|
1591
|
+
" 7. Anything off-limits (topics, companies, people), and how spicy can we get — safe, " +
|
|
1592
|
+
"opinionated, or provocative? (-> content_guardrails + voice.never)\n" +
|
|
1593
|
+
"Then SYNTHESIZE the fields from their dictation: search_topics comes PRIMARILY from answer 2 " +
|
|
1594
|
+
"(fold in recurring scan themes only as reinforcement); description/content_angle/voice from " +
|
|
1595
|
+
"the rest. Keep their RAW transcript VERBATIM as content_corpus (do NOT paraphrase; their " +
|
|
1596
|
+
"actual numbers, opinions, and phrasing are what make drafts sound like them). If the user " +
|
|
1597
|
+
"declines or gives nothing usable, fall back to scan-derived topics. " +
|
|
1598
|
+
"THIRD (engagement lanes — ASK THE USER, do not infer): the PERSONAL BRAND lane (organic, " +
|
|
1564
1599
|
"link-free engagement in their own voice) is ON by default, so ask the ONE question — do they " +
|
|
1565
1600
|
"ALSO want to PROMOTE a PRODUCT (the marketing lane, link replies)? Both lanes can run (the " +
|
|
1566
1601
|
"cycle splits 50/50). Call the `engagement_mode` tool action:'set' with personal_brand:true, " +
|
|
1567
|
-
"promotion:true|false AND the voice/description/
|
|
1568
|
-
"
|
|
1569
|
-
"
|
|
1602
|
+
"promotion:true|false AND the voice/description/search_topics you synthesized PLUS the raw " +
|
|
1603
|
+
"dictation transcript as content_corpus (this provisions the persona and seeds topics). Only " +
|
|
1604
|
+
"NOW are topics seeded — postponed until the dictation is in. " +
|
|
1605
|
+
"FOURTH (product, ONLY if they wanted promotion): follow " +
|
|
1570
1606
|
"website_research_instructions — discover the product URL from config, context, profile " +
|
|
1571
1607
|
"links/posts, or public research and read 5+ of its pages to fill description, " +
|
|
1572
1608
|
"differentiator, icp, get_started_link, and content_guardrails, written in the voice you " +
|
package/mcp/dist/setup.js
CHANGED
|
@@ -383,6 +383,20 @@ export function ensurePersonaProject(grounding) {
|
|
|
383
383
|
}
|
|
384
384
|
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
385
385
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
386
|
+
// Raw dictation transcript -> persona_corpus.txt sidecar (NOT config.json).
|
|
387
|
+
// Mirrors scripts/build_persona.py cmd_apply: config.json is inlined into many
|
|
388
|
+
// prompts (ALL_PROJECTS_JSON), so a multi-KB corpus there would bloat every
|
|
389
|
+
// cycle's token bill. The persona lane reads this file only when it drafts.
|
|
390
|
+
// Best-effort; a write failure must never block persona provisioning.
|
|
391
|
+
if (grounding && typeof grounding.content_corpus === "string" && grounding.content_corpus.trim()) {
|
|
392
|
+
try {
|
|
393
|
+
const corpusPath = path.join(path.dirname(cfgPath), "persona_corpus.txt");
|
|
394
|
+
fs.writeFileSync(corpusPath, grounding.content_corpus.trim().slice(0, 8000) + "\n", "utf-8");
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// ignore: corpus is grounding fuel, not required for a working persona.
|
|
398
|
+
}
|
|
399
|
+
}
|
|
386
400
|
return { name, created };
|
|
387
401
|
}
|
|
388
402
|
// Heal installs that onboarded BEFORE short_links_live defaulted to false.
|
package/mcp/dist/version.json
CHANGED
package/mcp/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dxt_version": "0.1",
|
|
3
3
|
"name": "social-autoposter",
|
|
4
4
|
"display_name": "S4L",
|
|
5
|
-
"version": "1.6.
|
|
5
|
+
"version": "1.6.186",
|
|
6
6
|
"description": "Draft, review, approve, and autopilot X/Twitter posts.",
|
|
7
7
|
"long_description": "## **⚠️ The disclaimer above is generic Claude boilerplate.** Anthropic shows the same warning on every plugin regardless of what it does; any plugin has the same level of access as any app you download from the internet.\n\nS4L is an open source product developed by Mediar.ai Incorporated, a VC-backed San Francisco-based startup.\n\nTo get started:\n\n1\\. Copy this prompt: **Set me up on S4L plugin end to end**\n\n2\\. Quit with CMD+Q, reopen Claude, paste into a new chat.\n\nWhat happens next:\n\n* About every 5 minutes S4L scans X for posts that match your topics and drafts replies in your voice.\n* Drafts show up as review cards, usually the first within a few minutes. Nothing is posted automatically; you approve each one.\n* Posting autopilot stays off until you explicitly turn it on.",
|
|
8
8
|
"author": {
|
|
@@ -315,9 +315,10 @@ class S4LMenuBar(rumps.App):
|
|
|
315
315
|
# read per second.
|
|
316
316
|
self._act_poll = rumps.Timer(self._poll_activity, 1.0)
|
|
317
317
|
self._act_poll.start()
|
|
318
|
-
# Update availability comes from ONE source:
|
|
319
|
-
# (
|
|
320
|
-
#
|
|
318
|
+
# Update availability comes from ONE source: scripts/snapshot.py's
|
|
319
|
+
# _latest_published (GitHub releases/latest first, npm fallback — boxes
|
|
320
|
+
# have no npm; same probe as mcp/src/version.ts), surfaced in the
|
|
321
|
+
# snapshot as update_available/latest_version. _tick copies those
|
|
321
322
|
# snapshot fields onto these attrs every poll. No second GitHub/manifest
|
|
322
323
|
# check here anymore (it diverged from the header and once showed an
|
|
323
324
|
# "update to an OLDER version" because it polled a different registry).
|
|
@@ -1431,9 +1432,9 @@ class S4LMenuBar(rumps.App):
|
|
|
1431
1432
|
elif not attention:
|
|
1432
1433
|
self._stall_notified = False
|
|
1433
1434
|
|
|
1434
|
-
# Single-source update signal: copy the snapshot's
|
|
1435
|
-
#
|
|
1436
|
-
#
|
|
1435
|
+
# Single-source update signal: copy the snapshot's result (snapshot.py
|
|
1436
|
+
# _latest_published: GitHub releases/latest first, npm fallback; semver >,
|
|
1437
|
+
# surfaced as update_available/latest_version). No separate poll here.
|
|
1437
1438
|
self._update_available = bool(snap.get("update_available"))
|
|
1438
1439
|
self._latest_version = snap.get("latest_version")
|
|
1439
1440
|
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/social-autoposter-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.186",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
|
|
6
6
|
"license": "MIT",
|
package/package.json
CHANGED
package/scripts/snapshot.py
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
Produces the SAME dict as the MCP's buildSnapshot() (mcp/src/index.ts), but in
|
|
5
5
|
Python, reading directly from the stateful files plus two existing Python helpers
|
|
6
|
-
(setup_twitter_auth.py for X, schedule_state.py for the draft schedule) and
|
|
7
|
-
for the latest version.
|
|
6
|
+
(setup_twitter_auth.py for X, schedule_state.py for the draft schedule) and
|
|
7
|
+
GitHub releases/latest (npm fallback) for the latest version.
|
|
8
8
|
|
|
9
9
|
WHY this exists: the menu bar must render with Claude / the MCP fully closed. The
|
|
10
10
|
MCP is a Node process tied to Claude Desktop's lifecycle, and it was the ONLY
|
|
@@ -17,8 +17,8 @@ to the whole snapshot. The source of truth is the FILES; this is just the reader
|
|
|
17
17
|
|
|
18
18
|
PURE READ/COMPUTE: never writes (no onboarding-milestone telemetry, no
|
|
19
19
|
persistence) — the MCP keeps those side effects around this. Slow fields (X
|
|
20
|
-
session,
|
|
21
|
-
imports this module stays cheap.
|
|
20
|
+
session, latest version) are cached per-process with a TTL so a 5s menu-bar tick
|
|
21
|
+
that imports this module stays cheap.
|
|
22
22
|
"""
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
@@ -259,10 +259,49 @@ def _x_status():
|
|
|
259
259
|
return val
|
|
260
260
|
|
|
261
261
|
|
|
262
|
-
# ---- version (resolveVersion +
|
|
263
|
-
|
|
262
|
+
# ---- version (resolveVersion + latest release + semver), cached ------------
|
|
263
|
+
# Latest-version SOURCE = GitHub releases/latest first, npm only as a fallback.
|
|
264
|
+
# This mirrors mcp/src/version.ts::latestPublishedVersion and is load-bearing:
|
|
265
|
+
# the .mcpb boxes that render the menu bar have NO npm on PATH (PATH is just
|
|
266
|
+
# /usr/bin:/bin:/usr/sbin:/sbin), so an npm-only probe always yields latest=None
|
|
267
|
+
# there, update_available is always False, and the "S4L ⬆" banner can never
|
|
268
|
+
# fire on a box — even with a new release live. The 2026-07-01 v1.6.182 fix
|
|
269
|
+
# closed this in version.ts only; the menu bar computes its snapshot through
|
|
270
|
+
# THIS module (mcp/menubar/s4l_state.py tier 1, the loopback tier was removed
|
|
271
|
+
# to fix a UI freeze), so the same probe must live here too. curl is at
|
|
272
|
+
# /usr/bin/curl on every macOS PATH. GitHub releases/latest is also the SAME
|
|
273
|
+
# source the box updater installs from (s4l_box_update.sh / _mcpb_update_work),
|
|
274
|
+
# so "update available" and "what an update installs" cannot disagree.
|
|
275
|
+
_ver_cache = {"at": 0.0, "latest": None, "checked": False}
|
|
264
276
|
_VER_TTL = 600.0
|
|
265
277
|
|
|
278
|
+
_RELEASES_LATEST_API = "https://api.github.com/repos/m13v/social-autoposter/releases/latest"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _latest_from_github():
|
|
282
|
+
try:
|
|
283
|
+
# -f: fail on HTTP error, -sSL: quiet + follow redirects, -m: hard timeout.
|
|
284
|
+
# releases/latest already excludes drafts and prereleases.
|
|
285
|
+
res = subprocess.run(
|
|
286
|
+
["/usr/bin/curl", "-fsSL", "-m", "10",
|
|
287
|
+
"-H", "Accept: application/vnd.github+json", _RELEASES_LATEST_API],
|
|
288
|
+
capture_output=True, text=True, timeout=12)
|
|
289
|
+
tag = (json.loads(res.stdout) or {}).get("tag_name")
|
|
290
|
+
v = tag.lstrip("v").strip() if isinstance(tag, str) else ""
|
|
291
|
+
return v if v and v[0].isdigit() and v.count(".") >= 2 else None
|
|
292
|
+
except Exception:
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _latest_from_npm():
|
|
297
|
+
try:
|
|
298
|
+
res = subprocess.run(["npm", "view", "social-autoposter", "version"],
|
|
299
|
+
capture_output=True, text=True, timeout=8)
|
|
300
|
+
line = (res.stdout.strip().splitlines() or [""])[-1].strip()
|
|
301
|
+
return line if line and line[0].isdigit() else None
|
|
302
|
+
except Exception:
|
|
303
|
+
return None
|
|
304
|
+
|
|
266
305
|
|
|
267
306
|
def _resolve_version() -> str:
|
|
268
307
|
for p in (
|
|
@@ -278,18 +317,15 @@ def _resolve_version() -> str:
|
|
|
278
317
|
|
|
279
318
|
def _latest_published():
|
|
280
319
|
now = time.time()
|
|
281
|
-
|
|
320
|
+
# Cache failures (latest=None) too, like version.ts: a menu-bar tick loop
|
|
321
|
+
# re-probing an unreachable/rate-limited GitHub every few seconds would burn
|
|
322
|
+
# the unauthenticated API quota (60/h per IP) and lock itself out for good.
|
|
323
|
+
if _ver_cache["checked"] and now - _ver_cache["at"] < _VER_TTL:
|
|
282
324
|
return _ver_cache["latest"]
|
|
283
|
-
latest =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
line = (res.stdout.strip().splitlines() or [""])[-1].strip()
|
|
288
|
-
if line and line[0].isdigit():
|
|
289
|
-
latest = line
|
|
290
|
-
except Exception:
|
|
291
|
-
latest = None
|
|
292
|
-
_ver_cache.update(at=now, latest=latest)
|
|
325
|
+
latest = _latest_from_github()
|
|
326
|
+
if latest is None:
|
|
327
|
+
latest = _latest_from_npm()
|
|
328
|
+
_ver_cache.update(at=now, latest=latest, checked=True)
|
|
293
329
|
return latest
|
|
294
330
|
|
|
295
331
|
|
package/setup/SKILL.md
CHANGED
|
@@ -42,8 +42,12 @@ Do not report setup complete until all of these are true:
|
|
|
42
42
|
|
|
43
43
|
1. The owned runtime is installed and ready.
|
|
44
44
|
2. At least one project is ready with name, website, description, ICP, voice,
|
|
45
|
-
and search topics.
|
|
46
|
-
|
|
45
|
+
and search topics. For the personal-brand persona, the search topics and the
|
|
46
|
+
grounding corpus come from the user's dictation interview (below), not from
|
|
47
|
+
the profile scan alone.
|
|
48
|
+
3. Search topics have been seeded into the backend. Seeding is POSTPONED until
|
|
49
|
+
after the dictation interview so the topics reflect what the user wants to be
|
|
50
|
+
in conversations about, not only what they already posted.
|
|
47
51
|
4. X is connected and the real handle has been auto-detected.
|
|
48
52
|
5. The draft autopilot is scheduled and has produced a draft card without
|
|
49
53
|
posting. A returned/pending review batch is the strongest success signal. If X
|
|
@@ -59,7 +63,10 @@ Do not report setup complete until all of these are true:
|
|
|
59
63
|
`DATABASE_URL`.
|
|
60
64
|
- **Search topics**: the X cycle reads `project_search_topics`, seeded
|
|
61
65
|
automatically from each project's `search_topics`. No topics means nothing
|
|
62
|
-
to scan.
|
|
66
|
+
to scan. `search_topics` is the ONLY field that changes what gets scanned;
|
|
67
|
+
every other persona field only shapes the draft after a thread is found. For
|
|
68
|
+
the persona, the profile scan is backward-looking (only what the user already
|
|
69
|
+
posted), so topics are sourced primarily from the dictation interview.
|
|
63
70
|
|
|
64
71
|
## Choose the path
|
|
65
72
|
|
|
@@ -104,17 +111,53 @@ If X is not connected:
|
|
|
104
111
|
`connect_x` after sign-in and continue.
|
|
105
112
|
|
|
106
113
|
Once connected, call `project_config` with `action:'profile_scan'`. Treat the returned
|
|
107
|
-
bio, links, recent posts, and replies as grounding truth for
|
|
114
|
+
bio, links, recent posts, and replies as grounding truth for the VOICE, not as the
|
|
115
|
+
primary source of topics (the scan only shows what they already posted):
|
|
108
116
|
|
|
109
117
|
- profession/identity;
|
|
110
118
|
- voice, casing, phrasing, and tone;
|
|
111
119
|
- ICP;
|
|
112
|
-
-
|
|
113
|
-
-
|
|
120
|
+
- wording or claims the user avoids;
|
|
121
|
+
- recurring themes (reinforcement for topics, not the origin).
|
|
114
122
|
|
|
115
123
|
Do not ask the user to approve the inferred voice during initial setup. Save a
|
|
116
124
|
specific, conservative best draft and mention afterward that it can be edited.
|
|
117
125
|
|
|
126
|
+
Then run the DICTATION INTERVIEW before extracting topics or seeding. This is
|
|
127
|
+
where the persona's `search_topics` and grounding corpus come from. Tell the user
|
|
128
|
+
to answer all of the following in ONE spoken dictation (the Claude input box
|
|
129
|
+
already supports dictation, so they talk once and you split the answers into
|
|
130
|
+
fields). Ask verbatim as a single numbered list:
|
|
131
|
+
|
|
132
|
+
1. Who are you, and what do you want to be known for? (→ description)
|
|
133
|
+
2. What subjects could you talk about for an hour, work and non-work? (→
|
|
134
|
+
`search_topics`; this is the load-bearing answer, it is the only thing that
|
|
135
|
+
decides what gets scanned on X)
|
|
136
|
+
3. Your most contrarian takes — what does everyone in your field get wrong, and
|
|
137
|
+
what did you used to believe that you have reversed on? (→ content_angle +
|
|
138
|
+
corpus)
|
|
139
|
+
4. What can you explain in 5 minutes that took you years, and what mistake do you
|
|
140
|
+
watch beginners make over and over? (→ content_angle + corpus)
|
|
141
|
+
5. Best or worst thing that happened to you recently, and a failure you learned
|
|
142
|
+
the most from? (→ corpus, keeps drafts current)
|
|
143
|
+
6. Who do you love or hate reading online, and any lines or phrases you say a
|
|
144
|
+
lot? (→ voice calibration)
|
|
145
|
+
7. Anything off-limits (topics, companies, people), and how spicy can we get —
|
|
146
|
+
safe, opinionated, or provocative? (→ content_guardrails + voice.never)
|
|
147
|
+
|
|
148
|
+
From the dictation, synthesize the fields: `search_topics` primarily from answer
|
|
149
|
+
2 (fold in recurring scan themes only as reinforcement), the rest from the other
|
|
150
|
+
answers. Keep the RAW transcript VERBATIM as the persona corpus (do not
|
|
151
|
+
paraphrase; the actual numbers, opinions, and phrasing are what make drafts sound
|
|
152
|
+
like them). Pass it as `content_corpus` when you call `engagement_mode` (or
|
|
153
|
+
`project_config`); it is stored in the `persona_corpus.txt` sidecar, not
|
|
154
|
+
config.json. If the user declines or gives nothing usable, fall back to
|
|
155
|
+
scan-derived topics.
|
|
156
|
+
|
|
157
|
+
Only after the dictation is captured do you set the engagement mode / save the
|
|
158
|
+
persona, which seeds the topics. Do not seed topics from the scan before the
|
|
159
|
+
dictation.
|
|
160
|
+
|
|
118
161
|
### 3. Discover and research the product
|
|
119
162
|
|
|
120
163
|
Find the product URL in this order:
|
|
@@ -195,7 +238,9 @@ turning it into instructions for the user.
|
|
|
195
238
|
|
|
196
239
|
1. Install/repair: `npx -y social-autoposter@latest init`
|
|
197
240
|
2. Inspect `~/social-autoposter/config.json` and preserve existing projects.
|
|
198
|
-
3. Discover the product and voice using the same evidence order above.
|
|
241
|
+
3. Discover the product and voice using the same evidence order above. For a
|
|
242
|
+
personal-brand persona, run the dictation interview (the 7 questions above)
|
|
243
|
+
and take `search_topics` + the raw corpus from it, scan themes as backup.
|
|
199
244
|
4. Write a complete project with name, website, description, ICP, voice, and
|
|
200
245
|
`search_topics`.
|
|
201
246
|
5. Seed topics:
|