@npow/ghostwriter 0.1.0
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/dist/commands/connect.d.ts +30 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +442 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/create/index.d.ts +6 -0
- package/dist/commands/create/index.d.ts.map +1 -0
- package/dist/commands/create/index.js +89 -0
- package/dist/commands/create/index.js.map +1 -0
- package/dist/commands/create/prompts/example-article.d.ts +3 -0
- package/dist/commands/create/prompts/example-article.d.ts.map +1 -0
- package/dist/commands/create/prompts/example-article.js +32 -0
- package/dist/commands/create/prompts/example-article.js.map +1 -0
- package/dist/commands/create/prompts/intent-parsing.d.ts +3 -0
- package/dist/commands/create/prompts/intent-parsing.d.ts.map +1 -0
- package/dist/commands/create/prompts/intent-parsing.js +45 -0
- package/dist/commands/create/prompts/intent-parsing.js.map +1 -0
- package/dist/commands/create/prompts/page-content.d.ts +3 -0
- package/dist/commands/create/prompts/page-content.d.ts.map +1 -0
- package/dist/commands/create/prompts/page-content.js +56 -0
- package/dist/commands/create/prompts/page-content.js.map +1 -0
- package/dist/commands/create/prompts/source-discovery.d.ts +3 -0
- package/dist/commands/create/prompts/source-discovery.d.ts.map +1 -0
- package/dist/commands/create/prompts/source-discovery.js +46 -0
- package/dist/commands/create/prompts/source-discovery.js.map +1 -0
- package/dist/commands/create/prompts/voice-generation.d.ts +3 -0
- package/dist/commands/create/prompts/voice-generation.d.ts.map +1 -0
- package/dist/commands/create/prompts/voice-generation.js +43 -0
- package/dist/commands/create/prompts/voice-generation.js.map +1 -0
- package/dist/commands/create/steps/assemble-config.d.ts +5 -0
- package/dist/commands/create/steps/assemble-config.d.ts.map +1 -0
- package/dist/commands/create/steps/assemble-config.js +82 -0
- package/dist/commands/create/steps/assemble-config.js.map +1 -0
- package/dist/commands/create/steps/discover-sources.d.ts +5 -0
- package/dist/commands/create/steps/discover-sources.d.ts.map +1 -0
- package/dist/commands/create/steps/discover-sources.js +53 -0
- package/dist/commands/create/steps/discover-sources.js.map +1 -0
- package/dist/commands/create/steps/fingerprint-style.d.ts +5 -0
- package/dist/commands/create/steps/fingerprint-style.d.ts.map +1 -0
- package/dist/commands/create/steps/fingerprint-style.js +46 -0
- package/dist/commands/create/steps/fingerprint-style.js.map +1 -0
- package/dist/commands/create/steps/generate-example.d.ts +4 -0
- package/dist/commands/create/steps/generate-example.d.ts.map +1 -0
- package/dist/commands/create/steps/generate-example.js +23 -0
- package/dist/commands/create/steps/generate-example.js.map +1 -0
- package/dist/commands/create/steps/generate-voice.d.ts +4 -0
- package/dist/commands/create/steps/generate-voice.d.ts.map +1 -0
- package/dist/commands/create/steps/generate-voice.js +32 -0
- package/dist/commands/create/steps/generate-voice.js.map +1 -0
- package/dist/commands/create/steps/parse-intent.d.ts +4 -0
- package/dist/commands/create/steps/parse-intent.d.ts.map +1 -0
- package/dist/commands/create/steps/parse-intent.js +16 -0
- package/dist/commands/create/steps/parse-intent.js.map +1 -0
- package/dist/commands/create/steps/resolve-connection.d.ts +5 -0
- package/dist/commands/create/steps/resolve-connection.d.ts.map +1 -0
- package/dist/commands/create/steps/resolve-connection.js +100 -0
- package/dist/commands/create/steps/resolve-connection.js.map +1 -0
- package/dist/commands/create/steps/resolve-schedule.d.ts +13 -0
- package/dist/commands/create/steps/resolve-schedule.d.ts.map +1 -0
- package/dist/commands/create/steps/resolve-schedule.js +70 -0
- package/dist/commands/create/steps/resolve-schedule.js.map +1 -0
- package/dist/commands/create/steps/setup-site.d.ts +5 -0
- package/dist/commands/create/steps/setup-site.d.ts.map +1 -0
- package/dist/commands/create/steps/setup-site.js +44 -0
- package/dist/commands/create/steps/setup-site.js.map +1 -0
- package/dist/commands/create/steps/validate-and-summary.d.ts +4 -0
- package/dist/commands/create/steps/validate-and-summary.d.ts.map +1 -0
- package/dist/commands/create/steps/validate-and-summary.js +60 -0
- package/dist/commands/create/steps/validate-and-summary.js.map +1 -0
- package/dist/commands/create/steps/validate-site-access.d.ts +4 -0
- package/dist/commands/create/steps/validate-site-access.d.ts.map +1 -0
- package/dist/commands/create/steps/validate-site-access.js +25 -0
- package/dist/commands/create/steps/validate-site-access.js.map +1 -0
- package/dist/commands/create/types.d.ts +72 -0
- package/dist/commands/create/types.d.ts.map +1 -0
- package/dist/commands/create/types.js +2 -0
- package/dist/commands/create/types.js.map +1 -0
- package/dist/commands/fingerprint.d.ts +4 -0
- package/dist/commands/fingerprint.d.ts.map +1 -0
- package/dist/commands/fingerprint.js +31 -0
- package/dist/commands/fingerprint.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +117 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/logs.d.ts +6 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +49 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/patterns.d.ts +6 -0
- package/dist/commands/patterns.d.ts.map +1 -0
- package/dist/commands/patterns.js +51 -0
- package/dist/commands/patterns.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +139 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +66 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +83 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example-article.js","sourceRoot":"","sources":["../../../../src/commands/create/prompts/example-article.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;kEAa4B,CAAC;AAEnE,MAAM,UAAU,yBAAyB,CACvC,WAAmB,EACnB,WAAmB,EACnB,UAAkB,EAClB,SAAiB,EACjB,YAAoB,EACpB,UAAoB,EACpB,cAAwB,EACxB,cAAwB,EACxB,IAAY;IAEZ,OAAO,kBAAkB,WAAW,SAAS,WAAW;;eAE3C,UAAU;;SAEhB,SAAS;EAChB,YAAY;;gCAEkB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM;wBACvC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM;QACnD,IAAI;;;EAGV,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;qDAEG,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const INTENT_PARSING_SYSTEM = "You are a configuration parser for an autonomous blogging platform.\nGiven a natural language description of a blog channel, extract structured information.\n\nReturn ONLY valid JSON matching this schema (no markdown, no explanation):\n\n{\n \"channelId\": \"kebab-case-id\",\n \"channelName\": \"Human Readable Name\",\n \"contentType\": \"article|listicle|recap|analysis|tutorial|recipe|review|roundup\",\n \"topic\": {\n \"domain\": \"topic-domain\",\n \"focus\": \"One sentence describing the content focus\",\n \"keywords\": [\"keyword1\", \"keyword2\", \"keyword3\"],\n \"constraints\": \"Optional safety constraints or content restrictions\"\n },\n \"toneDescription\": \"Description of the desired tone/style\",\n \"styleReferences\": [\"URL or writer/publication name for style reference\"],\n \"publishPlatform\": \"wordpress-com\",\n \"siteUrl\": \"site URL if mentioned\",\n \"connectionId\": \"connection ID if mentioned\",\n \"schedule\": {\n \"frequency\": \"daily|weekly|biweekly|monthly\",\n \"dayOfWeek\": \"optional day like Monday, Saturday\",\n \"time\": \"optional time like 9am, 10:00\",\n \"timezone\": \"optional timezone like America/New_York\"\n },\n \"targetWordCount\": null\n}\n\nRules:\n- channelId: derive from the topic, 2-4 words, kebab-case (e.g. \"weekly-stock-recap\")\n- contentType: pick the best match from the enum values\n- If user mentions a writer by name (e.g. \"like Matt Levine\"), put their name in styleReferences\n- If user provides a URL, put it in styleReferences\n- publishPlatform: \"wordpress-com\" for WordPress.com sites\n- siteUrl: extract the full domain if mentioned (e.g. \"marketpulse.wordpress.com\")\n- schedule: infer from frequency words. Default to weekly if a recap/roundup, daily otherwise\n- constraints: add \"Never give financial advice\" for finance topics, \"Recipes must include measurements\" for recipe topics, etc.\n- targetWordCount: null unless explicitly mentioned. The system will default to 1500.";
|
|
2
|
+
export declare function buildIntentParsingPrompt(description: string): string;
|
|
3
|
+
//# sourceMappingURL=intent-parsing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-parsing.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/prompts/intent-parsing.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,q+DAsCoD,CAAC;AAEvF,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAIpE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const INTENT_PARSING_SYSTEM = `You are a configuration parser for an autonomous blogging platform.
|
|
2
|
+
Given a natural language description of a blog channel, extract structured information.
|
|
3
|
+
|
|
4
|
+
Return ONLY valid JSON matching this schema (no markdown, no explanation):
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
"channelId": "kebab-case-id",
|
|
8
|
+
"channelName": "Human Readable Name",
|
|
9
|
+
"contentType": "article|listicle|recap|analysis|tutorial|recipe|review|roundup",
|
|
10
|
+
"topic": {
|
|
11
|
+
"domain": "topic-domain",
|
|
12
|
+
"focus": "One sentence describing the content focus",
|
|
13
|
+
"keywords": ["keyword1", "keyword2", "keyword3"],
|
|
14
|
+
"constraints": "Optional safety constraints or content restrictions"
|
|
15
|
+
},
|
|
16
|
+
"toneDescription": "Description of the desired tone/style",
|
|
17
|
+
"styleReferences": ["URL or writer/publication name for style reference"],
|
|
18
|
+
"publishPlatform": "wordpress-com",
|
|
19
|
+
"siteUrl": "site URL if mentioned",
|
|
20
|
+
"connectionId": "connection ID if mentioned",
|
|
21
|
+
"schedule": {
|
|
22
|
+
"frequency": "daily|weekly|biweekly|monthly",
|
|
23
|
+
"dayOfWeek": "optional day like Monday, Saturday",
|
|
24
|
+
"time": "optional time like 9am, 10:00",
|
|
25
|
+
"timezone": "optional timezone like America/New_York"
|
|
26
|
+
},
|
|
27
|
+
"targetWordCount": null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Rules:
|
|
31
|
+
- channelId: derive from the topic, 2-4 words, kebab-case (e.g. "weekly-stock-recap")
|
|
32
|
+
- contentType: pick the best match from the enum values
|
|
33
|
+
- If user mentions a writer by name (e.g. "like Matt Levine"), put their name in styleReferences
|
|
34
|
+
- If user provides a URL, put it in styleReferences
|
|
35
|
+
- publishPlatform: "wordpress-com" for WordPress.com sites
|
|
36
|
+
- siteUrl: extract the full domain if mentioned (e.g. "marketpulse.wordpress.com")
|
|
37
|
+
- schedule: infer from frequency words. Default to weekly if a recap/roundup, daily otherwise
|
|
38
|
+
- constraints: add "Never give financial advice" for finance topics, "Recipes must include measurements" for recipe topics, etc.
|
|
39
|
+
- targetWordCount: null unless explicitly mentioned. The system will default to 1500.`;
|
|
40
|
+
export function buildIntentParsingPrompt(description) {
|
|
41
|
+
return `Parse this channel description into structured configuration:
|
|
42
|
+
|
|
43
|
+
"${description}"`;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=intent-parsing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-parsing.js","sourceRoot":"","sources":["../../../../src/commands/create/prompts/intent-parsing.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sFAsCiD,CAAC;AAEvF,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,OAAO;;GAEN,WAAW,GAAG,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const PAGE_CONTENT_SYSTEM = "You are a web content writer for a blog.\nGenerate HTML content for site pages (About, Contact, etc.).\nWrite in the voice/tone described. Keep it concise and authentic.\n\nReturn ONLY valid JSON matching this schema (no markdown, no explanation):\n\n{\n \"categories\": [\n { \"name\": \"Category Name\", \"slug\": \"category-slug\", \"description\": \"Brief description\" }\n ],\n \"tags\": [\n { \"name\": \"Tag Name\", \"slug\": \"tag-slug\" }\n ],\n \"pages\": [\n {\n \"title\": \"Page Title\",\n \"slug\": \"page-slug\",\n \"content\": \"<p>HTML content here</p>\",\n \"status\": \"publish\"\n }\n ],\n \"siteIdentity\": {\n \"title\": \"Site Title\",\n \"tagline\": \"Site tagline\"\n },\n \"menus\": [\n {\n \"name\": \"Main Navigation\",\n \"location\": \"primary\",\n \"items\": [\n { \"title\": \"Home\", \"type\": \"custom\", \"url\": \"/\" },\n { \"title\": \"About\", \"type\": \"page\", \"objectSlug\": \"about\" }\n ]\n }\n ]\n}\n\nRules:\n- Generate 3-5 relevant categories based on the topic\n- Generate 5-8 relevant tags\n- Always create an \"About\" page and a \"Contact\" page\n- About page should be written in the blog's voice/tone, 150-250 words\n- Contact page should include a simple contact message\n- Site title should be the channel name\n- Tagline should be catchy and relevant (under 10 words)\n- Primary menu should link to Home, About, and the main categories";
|
|
2
|
+
export declare function buildPageContentPrompt(channelName: string, topicDomain: string, topicFocus: string, toneDescription: string, voicePersona: string): string;
|
|
3
|
+
//# sourceMappingURL=page-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-content.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/prompts/page-content.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,88CA6CmC,CAAC;AAEpE,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,GACnB,MAAM,CAQR"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const PAGE_CONTENT_SYSTEM = `You are a web content writer for a blog.
|
|
2
|
+
Generate HTML content for site pages (About, Contact, etc.).
|
|
3
|
+
Write in the voice/tone described. Keep it concise and authentic.
|
|
4
|
+
|
|
5
|
+
Return ONLY valid JSON matching this schema (no markdown, no explanation):
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
"categories": [
|
|
9
|
+
{ "name": "Category Name", "slug": "category-slug", "description": "Brief description" }
|
|
10
|
+
],
|
|
11
|
+
"tags": [
|
|
12
|
+
{ "name": "Tag Name", "slug": "tag-slug" }
|
|
13
|
+
],
|
|
14
|
+
"pages": [
|
|
15
|
+
{
|
|
16
|
+
"title": "Page Title",
|
|
17
|
+
"slug": "page-slug",
|
|
18
|
+
"content": "<p>HTML content here</p>",
|
|
19
|
+
"status": "publish"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"siteIdentity": {
|
|
23
|
+
"title": "Site Title",
|
|
24
|
+
"tagline": "Site tagline"
|
|
25
|
+
},
|
|
26
|
+
"menus": [
|
|
27
|
+
{
|
|
28
|
+
"name": "Main Navigation",
|
|
29
|
+
"location": "primary",
|
|
30
|
+
"items": [
|
|
31
|
+
{ "title": "Home", "type": "custom", "url": "/" },
|
|
32
|
+
{ "title": "About", "type": "page", "objectSlug": "about" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Rules:
|
|
39
|
+
- Generate 3-5 relevant categories based on the topic
|
|
40
|
+
- Generate 5-8 relevant tags
|
|
41
|
+
- Always create an "About" page and a "Contact" page
|
|
42
|
+
- About page should be written in the blog's voice/tone, 150-250 words
|
|
43
|
+
- Contact page should include a simple contact message
|
|
44
|
+
- Site title should be the channel name
|
|
45
|
+
- Tagline should be catchy and relevant (under 10 words)
|
|
46
|
+
- Primary menu should link to Home, About, and the main categories`;
|
|
47
|
+
export function buildPageContentPrompt(channelName, topicDomain, topicFocus, toneDescription, voicePersona) {
|
|
48
|
+
return `Generate site setup content for: ${channelName}
|
|
49
|
+
|
|
50
|
+
Topic: ${topicDomain} — ${topicFocus}
|
|
51
|
+
Tone: ${toneDescription}
|
|
52
|
+
Voice persona: ${voicePersona}
|
|
53
|
+
|
|
54
|
+
Create categories, tags, About/Contact pages, site identity, and navigation menu.`;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=page-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-content.js","sourceRoot":"","sources":["../../../../src/commands/create/prompts/page-content.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mEA6CgC,CAAC;AAEpE,MAAM,UAAU,sBAAsB,CACpC,WAAmB,EACnB,WAAmB,EACnB,UAAkB,EAClB,eAAuB,EACvB,YAAoB;IAEpB,OAAO,oCAAoC,WAAW;;SAE/C,WAAW,MAAM,UAAU;QAC5B,eAAe;iBACN,YAAY;;kFAEqD,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const SOURCE_DISCOVERY_SYSTEM = "You are a data source researcher for an AI blogging platform.\nGiven a topic domain and keywords, suggest real RSS feeds and API data sources\nthat would provide fresh content for writing about this topic.\n\nReturn ONLY valid JSON matching this schema (no markdown, no explanation):\n\n{\n \"sources\": [\n {\n \"type\": \"rss\",\n \"url\": \"https://example.com/feed\",\n \"name\": \"Source Name\",\n \"description\": \"What this source provides\"\n },\n {\n \"type\": \"api\",\n \"url\": \"https://api.example.com/v1/endpoint\",\n \"name\": \"API Name\",\n \"description\": \"What data this API provides\",\n \"requiresApiKey\": true,\n \"apiKeyEnvVar\": \"EXAMPLE_API_KEY\"\n }\n ]\n}\n\nRules:\n- Suggest 3-6 sources total, prioritizing RSS feeds (they're free and easy)\n- Only suggest REAL, working RSS feed URLs that you are confident exist\n- Common reliable RSS feeds include:\n - Major news: Reuters, AP News, BBC, NPR\n - Tech: Hacker News, TechCrunch, The Verge, Ars Technica\n - Finance: Yahoo Finance, Bloomberg, MarketWatch, CNBC\n - Science: Nature, Science Daily, Phys.org\n - Food/recipes: Serious Eats, Bon Appetit, Food52\n- For RSS, use the actual feed URL (usually /feed, /rss, /feed.xml)\n- For APIs, flag if they require API keys and what env var to set\n- Prefer diverse sources (don't just suggest 5 feeds from the same publisher)";
|
|
2
|
+
export declare function buildSourceDiscoveryPrompt(domain: string, keywords: string[], focus: string): string;
|
|
3
|
+
//# sourceMappingURL=source-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-discovery.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/prompts/source-discovery.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,u5CAoC0C,CAAC;AAE/E,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAAE,EAClB,KAAK,EAAE,MAAM,GACZ,MAAM,CAOR"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const SOURCE_DISCOVERY_SYSTEM = `You are a data source researcher for an AI blogging platform.
|
|
2
|
+
Given a topic domain and keywords, suggest real RSS feeds and API data sources
|
|
3
|
+
that would provide fresh content for writing about this topic.
|
|
4
|
+
|
|
5
|
+
Return ONLY valid JSON matching this schema (no markdown, no explanation):
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
"sources": [
|
|
9
|
+
{
|
|
10
|
+
"type": "rss",
|
|
11
|
+
"url": "https://example.com/feed",
|
|
12
|
+
"name": "Source Name",
|
|
13
|
+
"description": "What this source provides"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "api",
|
|
17
|
+
"url": "https://api.example.com/v1/endpoint",
|
|
18
|
+
"name": "API Name",
|
|
19
|
+
"description": "What data this API provides",
|
|
20
|
+
"requiresApiKey": true,
|
|
21
|
+
"apiKeyEnvVar": "EXAMPLE_API_KEY"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Rules:
|
|
27
|
+
- Suggest 3-6 sources total, prioritizing RSS feeds (they're free and easy)
|
|
28
|
+
- Only suggest REAL, working RSS feed URLs that you are confident exist
|
|
29
|
+
- Common reliable RSS feeds include:
|
|
30
|
+
- Major news: Reuters, AP News, BBC, NPR
|
|
31
|
+
- Tech: Hacker News, TechCrunch, The Verge, Ars Technica
|
|
32
|
+
- Finance: Yahoo Finance, Bloomberg, MarketWatch, CNBC
|
|
33
|
+
- Science: Nature, Science Daily, Phys.org
|
|
34
|
+
- Food/recipes: Serious Eats, Bon Appetit, Food52
|
|
35
|
+
- For RSS, use the actual feed URL (usually /feed, /rss, /feed.xml)
|
|
36
|
+
- For APIs, flag if they require API keys and what env var to set
|
|
37
|
+
- Prefer diverse sources (don't just suggest 5 feeds from the same publisher)`;
|
|
38
|
+
export function buildSourceDiscoveryPrompt(domain, keywords, focus) {
|
|
39
|
+
return `Find data sources for a blog about: ${domain}
|
|
40
|
+
|
|
41
|
+
Focus: ${focus}
|
|
42
|
+
Keywords: ${keywords.join(", ")}
|
|
43
|
+
|
|
44
|
+
Suggest real RSS feeds and APIs that would provide relevant content.`;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=source-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-discovery.js","sourceRoot":"","sources":["../../../../src/commands/create/prompts/source-discovery.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8EAoCuC,CAAC;AAE/E,MAAM,UAAU,0BAA0B,CACxC,MAAc,EACd,QAAkB,EAClB,KAAa;IAEb,OAAO,uCAAuC,MAAM;;SAE7C,KAAK;YACF,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;;qEAEsC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const VOICE_GENERATION_SYSTEM = "You are a character designer for an AI blogging platform.\nGiven a style profile (writing metrics), a tone description, and a topic domain,\ncreate a compelling writer persona.\n\nReturn ONLY valid JSON matching this schema (no markdown, no explanation):\n\n{\n \"name\": \"Character Name (first name + optional descriptor like 'the Market Guy')\",\n \"persona\": \"2-3 sentence description of who this person is and how they write\",\n \"age\": 30,\n \"backstory\": \"2-3 sentences about their background that explains their expertise\",\n \"opinions\": [\"Strong opinion 1\", \"Strong opinion 2\", \"Strong opinion 3\"],\n \"verbalTics\": [\"Catchphrase 1\", \"Catchphrase 2\"],\n \"vocabulary\": {\n \"preferred\": [\"word1\", \"word2\", \"word3\"],\n \"forbidden\": [\"forbidden1\", \"forbidden2\"]\n },\n \"tone\": \"conversational|professional|academic|casual|authoritative|humorous|warm\"\n}\n\nRules:\n- The persona should feel like a real person, not a generic AI\n- Opinions should be specific and relevant to the topic domain\n- Verbal tics should be natural conversational phrases, not clich\u00E9s\n- Preferred vocabulary should match the domain expertise\n- Forbidden words MUST include all AI-typical phrases from the blacklist provided\n- Tone must be one of: conversational, professional, academic, casual, authoritative, humorous, warm\n- Pick a tone that matches the tone description most closely\n- Age should be believable for someone with the described expertise";
|
|
2
|
+
export declare function buildVoiceGenerationPrompt(toneDescription: string, topicDomain: string, styleProfileText: string, forbiddenPhrases: string[]): string;
|
|
3
|
+
//# sourceMappingURL=voice-generation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-generation.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/prompts/voice-generation.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,g+CA4BgC,CAAC;AAErE,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,EACxB,gBAAgB,EAAE,MAAM,EAAE,GACzB,MAAM,CAYR"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const VOICE_GENERATION_SYSTEM = `You are a character designer for an AI blogging platform.
|
|
2
|
+
Given a style profile (writing metrics), a tone description, and a topic domain,
|
|
3
|
+
create a compelling writer persona.
|
|
4
|
+
|
|
5
|
+
Return ONLY valid JSON matching this schema (no markdown, no explanation):
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
"name": "Character Name (first name + optional descriptor like 'the Market Guy')",
|
|
9
|
+
"persona": "2-3 sentence description of who this person is and how they write",
|
|
10
|
+
"age": 30,
|
|
11
|
+
"backstory": "2-3 sentences about their background that explains their expertise",
|
|
12
|
+
"opinions": ["Strong opinion 1", "Strong opinion 2", "Strong opinion 3"],
|
|
13
|
+
"verbalTics": ["Catchphrase 1", "Catchphrase 2"],
|
|
14
|
+
"vocabulary": {
|
|
15
|
+
"preferred": ["word1", "word2", "word3"],
|
|
16
|
+
"forbidden": ["forbidden1", "forbidden2"]
|
|
17
|
+
},
|
|
18
|
+
"tone": "conversational|professional|academic|casual|authoritative|humorous|warm"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
- The persona should feel like a real person, not a generic AI
|
|
23
|
+
- Opinions should be specific and relevant to the topic domain
|
|
24
|
+
- Verbal tics should be natural conversational phrases, not clichés
|
|
25
|
+
- Preferred vocabulary should match the domain expertise
|
|
26
|
+
- Forbidden words MUST include all AI-typical phrases from the blacklist provided
|
|
27
|
+
- Tone must be one of: conversational, professional, academic, casual, authoritative, humorous, warm
|
|
28
|
+
- Pick a tone that matches the tone description most closely
|
|
29
|
+
- Age should be believable for someone with the described expertise`;
|
|
30
|
+
export function buildVoiceGenerationPrompt(toneDescription, topicDomain, styleProfileText, forbiddenPhrases) {
|
|
31
|
+
return `Create a writer persona for a ${topicDomain} blog.
|
|
32
|
+
|
|
33
|
+
Desired tone: ${toneDescription}
|
|
34
|
+
|
|
35
|
+
Style profile of reference writing:
|
|
36
|
+
${styleProfileText}
|
|
37
|
+
|
|
38
|
+
The "forbidden" vocabulary list MUST include at least these AI-typical phrases:
|
|
39
|
+
${forbiddenPhrases.map((p) => `- "${p}"`).join("\n")}
|
|
40
|
+
|
|
41
|
+
Generate a character that would naturally write in this style about ${topicDomain}.`;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=voice-generation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-generation.js","sourceRoot":"","sources":["../../../../src/commands/create/prompts/voice-generation.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEA4B6B,CAAC;AAErE,MAAM,UAAU,0BAA0B,CACxC,eAAuB,EACvB,WAAmB,EACnB,gBAAwB,EACxB,gBAA0B;IAE1B,OAAO,iCAAiC,WAAW;;gBAErC,eAAe;;;EAG7B,gBAAgB;;;EAGhB,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;sEAEkB,WAAW,GAAG,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Ora } from "ora";
|
|
2
|
+
import { type ChannelConfig } from "@ghostwriter/core";
|
|
3
|
+
import type { CreateContext } from "../types.js";
|
|
4
|
+
export declare function assembleConfig(ctx: CreateContext, spinner: Ora): Promise<ChannelConfig>;
|
|
5
|
+
//# sourceMappingURL=assemble-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-config.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/steps/assemble-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,aAAa,CAAC,CAsFxB"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { stringify as yamlStringify } from "yaml";
|
|
4
|
+
import { getChannelsDir, ChannelConfigSchema } from "@ghostwriter/core";
|
|
5
|
+
export async function assembleConfig(ctx, spinner) {
|
|
6
|
+
spinner.start("Assembling channel config...");
|
|
7
|
+
const intent = ctx.intent;
|
|
8
|
+
const voice = ctx.voice;
|
|
9
|
+
const schedule = ctx.schedule;
|
|
10
|
+
const siteUrl = intent.siteUrl
|
|
11
|
+
? `https://${intent.siteUrl.replace(/^https?:\/\//, "")}`
|
|
12
|
+
: ctx.connection?.url ?? "https://example.wordpress.com";
|
|
13
|
+
const config = ChannelConfigSchema.parse({
|
|
14
|
+
id: intent.channelId,
|
|
15
|
+
name: intent.channelName,
|
|
16
|
+
contentType: intent.contentType,
|
|
17
|
+
topic: {
|
|
18
|
+
domain: intent.topic.domain,
|
|
19
|
+
focus: intent.topic.focus,
|
|
20
|
+
keywords: intent.topic.keywords,
|
|
21
|
+
constraints: intent.topic.constraints,
|
|
22
|
+
},
|
|
23
|
+
dataSources: ctx.dataSources,
|
|
24
|
+
voice: {
|
|
25
|
+
name: voice.name,
|
|
26
|
+
persona: voice.persona,
|
|
27
|
+
age: voice.age,
|
|
28
|
+
backstory: voice.backstory,
|
|
29
|
+
opinions: voice.opinions,
|
|
30
|
+
verbalTics: voice.verbalTics,
|
|
31
|
+
exampleContent: ["./examples/sample-1.md"],
|
|
32
|
+
vocabulary: voice.vocabulary,
|
|
33
|
+
tone: voice.tone,
|
|
34
|
+
},
|
|
35
|
+
publishTargets: [
|
|
36
|
+
{
|
|
37
|
+
platform: "wordpress",
|
|
38
|
+
id: ctx.connection?.id,
|
|
39
|
+
url: siteUrl,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
schedule: {
|
|
43
|
+
cron: schedule.cron,
|
|
44
|
+
timezone: schedule.timezone,
|
|
45
|
+
enabled: true,
|
|
46
|
+
},
|
|
47
|
+
qualityGate: {
|
|
48
|
+
minScores: {
|
|
49
|
+
structure: 7,
|
|
50
|
+
readability: 7,
|
|
51
|
+
voiceMatch: 7,
|
|
52
|
+
factualAccuracy: 7,
|
|
53
|
+
sourceCoverage: 7,
|
|
54
|
+
hookStrength: 7,
|
|
55
|
+
engagementPotential: 7,
|
|
56
|
+
naturalness: 7,
|
|
57
|
+
perplexityVariance: 7,
|
|
58
|
+
},
|
|
59
|
+
maxRevisions: 3,
|
|
60
|
+
},
|
|
61
|
+
targetWordCount: intent.targetWordCount ?? 1500,
|
|
62
|
+
batchApi: false,
|
|
63
|
+
});
|
|
64
|
+
if (ctx.options.dryRun) {
|
|
65
|
+
spinner.succeed("Config assembled (dry run — not writing files)");
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
// Write files
|
|
69
|
+
const channelsDir = getChannelsDir();
|
|
70
|
+
const channelDir = join(channelsDir, intent.channelId);
|
|
71
|
+
const examplesDir = join(channelDir, "examples");
|
|
72
|
+
await mkdir(examplesDir, { recursive: true });
|
|
73
|
+
const yamlContent = yamlStringify(config, {
|
|
74
|
+
lineWidth: 120,
|
|
75
|
+
defaultStringType: "QUOTE_DOUBLE",
|
|
76
|
+
defaultKeyType: "PLAIN",
|
|
77
|
+
});
|
|
78
|
+
await writeFile(join(channelDir, "config.yml"), yamlContent, "utf-8");
|
|
79
|
+
spinner.succeed(`Config written to channels/${intent.channelId}/config.yml`);
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=assemble-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-config.js","sourceRoot":"","sources":["../../../../src/commands/create/steps/assemble-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Ora } from "ora";
|
|
2
|
+
import type { DataSource } from "@ghostwriter/core";
|
|
3
|
+
import type { CreateContext } from "../types.js";
|
|
4
|
+
export declare function discoverSources(ctx: CreateContext, spinner: Ora): Promise<DataSource[]>;
|
|
5
|
+
//# sourceMappingURL=discover-sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-sources.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/steps/discover-sources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAMpE,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,UAAU,EAAE,CAAC,CAiEvB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { callLlmJson } from "@ghostwriter/content-pipeline";
|
|
2
|
+
import { validateRssFeed } from "@ghostwriter/data-ingestion";
|
|
3
|
+
import { SOURCE_DISCOVERY_SYSTEM, buildSourceDiscoveryPrompt, } from "../prompts/source-discovery.js";
|
|
4
|
+
export async function discoverSources(ctx, spinner) {
|
|
5
|
+
spinner.start("Discovering data sources...");
|
|
6
|
+
const intent = ctx.intent;
|
|
7
|
+
const { data, cost } = await callLlmJson("sonnet", SOURCE_DISCOVERY_SYSTEM, buildSourceDiscoveryPrompt(intent.topic.domain, intent.topic.keywords, intent.topic.focus), { temperature: 0.3 });
|
|
8
|
+
ctx.totalCost += cost;
|
|
9
|
+
// Validate RSS feeds
|
|
10
|
+
const validSources = [];
|
|
11
|
+
let validated = 0;
|
|
12
|
+
let failed = 0;
|
|
13
|
+
for (const source of data.sources) {
|
|
14
|
+
if (source.type === "rss") {
|
|
15
|
+
spinner.text = `Validating RSS: ${source.name}...`;
|
|
16
|
+
const result = await validateRssFeed(source.url);
|
|
17
|
+
if (result.valid) {
|
|
18
|
+
validSources.push({
|
|
19
|
+
type: "rss",
|
|
20
|
+
url: source.url,
|
|
21
|
+
maxItems: 10,
|
|
22
|
+
});
|
|
23
|
+
validated++;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
failed++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (source.type === "api") {
|
|
30
|
+
// API sources are included but may need API keys
|
|
31
|
+
validSources.push({
|
|
32
|
+
type: "api",
|
|
33
|
+
provider: source.name.toLowerCase().replace(/\s+/g, "-"),
|
|
34
|
+
endpoint: source.url,
|
|
35
|
+
headers: source.apiKeyEnvVar
|
|
36
|
+
? { Authorization: `Bearer \${${source.apiKeyEnvVar}}` }
|
|
37
|
+
: undefined,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (validSources.length === 0) {
|
|
42
|
+
// Fallback: create a generic RSS source from the domain
|
|
43
|
+
spinner.warn("No sources validated — adding a placeholder RSS source");
|
|
44
|
+
validSources.push({
|
|
45
|
+
type: "rss",
|
|
46
|
+
url: `https://news.google.com/rss/search?q=${encodeURIComponent(intent.topic.keywords[0] ?? intent.topic.domain)}`,
|
|
47
|
+
maxItems: 10,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
spinner.succeed(`Found ${validSources.length} source(s) (${validated} RSS validated, ${failed} failed)`);
|
|
51
|
+
return validSources;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=discover-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover-sources.js","sourceRoot":"","sources":["../../../../src/commands/create/steps/discover-sources.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EACL,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAkB,EAClB,OAAY;IAEZ,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAE7C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAO,CAAC;IAE3B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,WAAW,CACtC,QAAQ,EACR,uBAAuB,EACvB,0BAA0B,CACxB,MAAM,CAAC,KAAK,CAAC,MAAM,EACnB,MAAM,CAAC,KAAK,CAAC,QAAQ,EACrB,MAAM,CAAC,KAAK,CAAC,KAAK,CACnB,EACD,EAAE,WAAW,EAAE,GAAG,EAAE,CACrB,CAAC;IAEF,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC;IAEtB,qBAAqB;IACrB,MAAM,YAAY,GAAiB,EAAE,CAAC;IACtC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,GAAG,mBAAmB,MAAM,CAAC,IAAI,KAAK,CAAC;YACnD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,YAAY,CAAC,IAAI,CAAC;oBAChB,IAAI,EAAE,KAAK;oBACX,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,QAAQ,EAAE,EAAE;iBACb,CAAC,CAAC;gBACH,SAAS,EAAE,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,MAAM,EAAE,CAAC;YACX,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACjC,iDAAiD;YACjD,YAAY,CAAC,IAAI,CAAC;gBAChB,IAAI,EAAE,KAAK;gBACX,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;gBACxD,QAAQ,EAAE,MAAM,CAAC,GAAG;gBACpB,OAAO,EAAE,MAAM,CAAC,YAAY;oBAC1B,CAAC,CAAC,EAAE,aAAa,EAAE,aAAa,MAAM,CAAC,YAAY,GAAG,EAAE;oBACxD,CAAC,CAAC,SAAS;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,wDAAwD;QACxD,OAAO,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACvE,YAAY,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,wCAAwC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;YAClH,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,OAAO,CACb,SAAS,YAAY,CAAC,MAAM,eAAe,SAAS,mBAAmB,MAAM,UAAU,CACxF,CAAC;IAEF,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Ora } from "ora";
|
|
2
|
+
import { type StyleProfile } from "@ghostwriter/style-fingerprint";
|
|
3
|
+
import type { CreateContext } from "../types.js";
|
|
4
|
+
export declare function fingerprintStyle(ctx: CreateContext, spinner: Ora): Promise<StyleProfile | undefined>;
|
|
5
|
+
//# sourceMappingURL=fingerprint-style.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint-style.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/steps/fingerprint-style.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAsCnC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { analyzeUrl, mergeStyleProfiles, formatStyleProfile, } from "@ghostwriter/style-fingerprint";
|
|
2
|
+
export async function fingerprintStyle(ctx, spinner) {
|
|
3
|
+
const refs = ctx.intent?.styleReferences ?? [];
|
|
4
|
+
if (refs.length === 0) {
|
|
5
|
+
spinner.info("No style references — will generate voice from description only");
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
spinner.start(`Fingerprinting style from ${refs.length} reference(s)...`);
|
|
9
|
+
const profiles = [];
|
|
10
|
+
for (const ref of refs) {
|
|
11
|
+
// Check if it's a URL
|
|
12
|
+
if (isUrl(ref)) {
|
|
13
|
+
try {
|
|
14
|
+
const profile = await analyzeUrl(ref);
|
|
15
|
+
profiles.push(profile);
|
|
16
|
+
spinner.text = `Fingerprinted: ${ref} (${profile.sampleCount} samples)`;
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
spinner.text = `Could not analyze ${ref}: ${err instanceof Error ? err.message : String(err)}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// It's a name reference (e.g. "Matt Levine") — skip URL resolution
|
|
24
|
+
// The voice generation step will use the name as context
|
|
25
|
+
spinner.text = `Style reference "${ref}" is a name — will use for voice generation`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (profiles.length === 0) {
|
|
29
|
+
spinner.info("No URLs could be fingerprinted — will rely on description for voice");
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const merged = profiles.length === 1 ? profiles[0] : mergeStyleProfiles(profiles);
|
|
33
|
+
const summary = formatStyleProfile(merged, "compact");
|
|
34
|
+
spinner.succeed(`Style fingerprint: ${summary}`);
|
|
35
|
+
return merged;
|
|
36
|
+
}
|
|
37
|
+
function isUrl(str) {
|
|
38
|
+
try {
|
|
39
|
+
const url = new URL(str.startsWith("http") ? str : `https://${str}`);
|
|
40
|
+
return url.hostname.includes(".");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=fingerprint-style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint-style.js","sourceRoot":"","sources":["../../../../src/commands/create/steps/fingerprint-style.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,gCAAgC,CAAC;AAGxC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAkB,EAClB,OAAY;IAEZ,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,IAAI,EAAE,CAAC;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;QAChF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,6BAA6B,IAAI,CAAC,MAAM,kBAAkB,CAAC,CAAC;IAE1E,MAAM,QAAQ,GAAmB,EAAE,CAAC;IAEpC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,sBAAsB;QACtB,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;gBACtC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACvB,OAAO,CAAC,IAAI,GAAG,kBAAkB,GAAG,KAAK,OAAO,CAAC,WAAW,WAAW,CAAC;YAC1E,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,GAAG,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACjG,CAAC;QACH,CAAC;aAAM,CAAC;YACN,mEAAmE;YACnE,yDAAyD;YACzD,OAAO,CAAC,IAAI,GAAG,oBAAoB,GAAG,6CAA6C,CAAC;QACtF,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;QACpF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAClF,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtD,OAAO,CAAC,OAAO,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAC;IAEjD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC;QACrE,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-example.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/steps/generate-example.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { callLlm } from "@ghostwriter/content-pipeline";
|
|
4
|
+
import { getChannelsDir } from "@ghostwriter/core";
|
|
5
|
+
import { EXAMPLE_ARTICLE_SYSTEM, buildExampleArticlePrompt, } from "../prompts/example-article.js";
|
|
6
|
+
export async function generateExample(ctx, spinner) {
|
|
7
|
+
spinner.start("Generating sample article...");
|
|
8
|
+
const intent = ctx.intent;
|
|
9
|
+
const voice = ctx.voice;
|
|
10
|
+
const result = await callLlm("sonnet", EXAMPLE_ARTICLE_SYSTEM, buildExampleArticlePrompt(intent.channelName, intent.contentType, intent.topic.focus, voice.name, voice.persona, voice.verbalTics ?? [], voice.vocabulary.preferred, voice.vocabulary.forbidden.slice(0, 30), // Don't overwhelm the prompt
|
|
11
|
+
voice.tone), { temperature: 0.8, maxTokens: 2048 });
|
|
12
|
+
ctx.totalCost += result.cost;
|
|
13
|
+
if (ctx.options.dryRun) {
|
|
14
|
+
spinner.succeed(`Sample article generated (${result.content.split(/\s+/).length} words, dry run — not writing)`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const channelsDir = getChannelsDir();
|
|
18
|
+
const examplePath = join(channelsDir, intent.channelId, "examples", "sample-1.md");
|
|
19
|
+
await writeFile(examplePath, result.content, "utf-8");
|
|
20
|
+
const wordCount = result.content.split(/\s+/).length;
|
|
21
|
+
spinner.succeed(`Sample article: ${wordCount} words → channels/${intent.channelId}/examples/sample-1.md`);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=generate-example.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-example.js","sourceRoot":"","sources":["../../../../src/commands/create/steps/generate-example.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EACL,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,+BAA+B,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAkB,EAClB,OAAY;IAEZ,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAE9C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAO,CAAC;IAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAM,CAAC;IAEzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,QAAQ,EACR,sBAAsB,EACtB,yBAAyB,CACvB,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,KAAK,CAAC,KAAK,EAClB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,UAAU,IAAI,EAAE,EACtB,KAAK,CAAC,UAAU,CAAC,SAAS,EAC1B,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,6BAA6B;IACtE,KAAK,CAAC,IAAI,CACX,EACD,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CACtC,CAAC;IAEF,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC;IAE7B,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,OAAO,CACb,6BAA6B,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,gCAAgC,CAChG,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,MAAM,WAAW,GAAG,IAAI,CACtB,WAAW,EACX,MAAM,CAAC,SAAS,EAChB,UAAU,EACV,aAAa,CACd,CAAC;IAEF,MAAM,SAAS,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEtD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IACrD,OAAO,CAAC,OAAO,CACb,mBAAmB,SAAS,qBAAqB,MAAM,CAAC,SAAS,uBAAuB,CACzF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-voice.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/steps/generate-voice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMjE,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { callLlmJson } from "@ghostwriter/content-pipeline";
|
|
2
|
+
import { AI_PHRASE_BLACKLIST } from "@ghostwriter/core";
|
|
3
|
+
import { formatStyleProfile } from "@ghostwriter/style-fingerprint";
|
|
4
|
+
import { VOICE_GENERATION_SYSTEM, buildVoiceGenerationPrompt, } from "../prompts/voice-generation.js";
|
|
5
|
+
export async function generateVoice(ctx, spinner) {
|
|
6
|
+
spinner.start("Generating voice persona...");
|
|
7
|
+
const intent = ctx.intent;
|
|
8
|
+
// Build style profile text for the prompt
|
|
9
|
+
let styleProfileText = "No style profile available — use the tone description.";
|
|
10
|
+
if (ctx.styleProfile) {
|
|
11
|
+
styleProfileText = formatStyleProfile(ctx.styleProfile, "prompt");
|
|
12
|
+
}
|
|
13
|
+
// Include style reference names as additional context
|
|
14
|
+
const nameRefs = intent.styleReferences.filter((ref) => !ref.startsWith("http"));
|
|
15
|
+
if (nameRefs.length > 0) {
|
|
16
|
+
styleProfileText += `\n\nThe writing style should be inspired by: ${nameRefs.join(", ")}`;
|
|
17
|
+
}
|
|
18
|
+
// Use a subset of the blacklist for the prompt (top 20 most common)
|
|
19
|
+
const forbiddenSample = AI_PHRASE_BLACKLIST.slice(0, 20);
|
|
20
|
+
const { data, cost } = await callLlmJson("sonnet", VOICE_GENERATION_SYSTEM, buildVoiceGenerationPrompt(intent.toneDescription, intent.topic.domain, styleProfileText, forbiddenSample), { temperature: 0.7 });
|
|
21
|
+
ctx.totalCost += cost;
|
|
22
|
+
// Ensure forbidden list includes the full blacklist
|
|
23
|
+
const existingForbidden = new Set(data.vocabulary.forbidden.map((w) => w.toLowerCase()));
|
|
24
|
+
for (const phrase of AI_PHRASE_BLACKLIST) {
|
|
25
|
+
if (!existingForbidden.has(phrase.toLowerCase())) {
|
|
26
|
+
data.vocabulary.forbidden.push(phrase);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
spinner.succeed(`Voice: ${data.name} — ${data.tone} tone`);
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=generate-voice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-voice.js","sourceRoot":"","sources":["../../../../src/commands/create/steps/generate-voice.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,EACL,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAkB,EAClB,OAAY;IAEZ,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAE7C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAO,CAAC;IAE3B,0CAA0C;IAC1C,IAAI,gBAAgB,GAAG,wDAAwD,CAAC;IAChF,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QACrB,gBAAgB,GAAG,kBAAkB,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACpE,CAAC;IAED,sDAAsD;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,MAAM,CAC5C,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CACjC,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,gBAAgB,IAAI,gDAAgD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC5F,CAAC;IAED,oEAAoE;IACpE,MAAM,eAAe,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEzD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,WAAW,CACtC,QAAQ,EACR,uBAAuB,EACvB,0BAA0B,CACxB,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,KAAK,CAAC,MAAM,EACnB,gBAAgB,EAChB,eAAe,CAChB,EACD,EAAE,WAAW,EAAE,GAAG,EAAE,CACrB,CAAC;IAEF,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC;IAEtB,oDAAoD;IACpD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAC/B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CACtD,CAAC;IACF,KAAK,MAAM,MAAM,IAAI,mBAAmB,EAAE,CAAC;QACzC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC;IAE3D,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-intent.d.ts","sourceRoot":"","sources":["../../../../src/commands/create/steps/parse-intent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM/D,wBAAsB,WAAW,CAC/B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,YAAY,CAAC,CAwBvB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { callLlmJson } from "@ghostwriter/content-pipeline";
|
|
2
|
+
import { INTENT_PARSING_SYSTEM, buildIntentParsingPrompt, } from "../prompts/intent-parsing.js";
|
|
3
|
+
export async function parseIntent(ctx, spinner) {
|
|
4
|
+
spinner.start("Parsing intent from description...");
|
|
5
|
+
const { data, cost } = await callLlmJson("sonnet", INTENT_PARSING_SYSTEM, buildIntentParsingPrompt(ctx.rawDescription), { temperature: 0.3 });
|
|
6
|
+
ctx.totalCost += cost;
|
|
7
|
+
// Ensure channelId is valid kebab-case
|
|
8
|
+
data.channelId = data.channelId
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
11
|
+
.replace(/-+/g, "-")
|
|
12
|
+
.replace(/^-|-$/g, "");
|
|
13
|
+
spinner.succeed(`Parsed: "${data.channelName}" (${data.contentType}) — ${data.topic.domain}`);
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=parse-intent.js.map
|