@astro-minimax/cli 0.7.0 → 0.7.2

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.
Files changed (53) hide show
  1. package/dist/commands/podcast.d.ts +2 -0
  2. package/dist/commands/podcast.d.ts.map +1 -0
  3. package/dist/commands/podcast.js +89 -0
  4. package/dist/commands/podcast.js.map +1 -0
  5. package/dist/commands/profile.d.ts.map +1 -1
  6. package/dist/commands/profile.js +7 -2
  7. package/dist/commands/profile.js.map +1 -1
  8. package/dist/tools/build-fact-registry.d.ts +14 -0
  9. package/dist/tools/build-fact-registry.d.ts.map +1 -0
  10. package/dist/tools/build-fact-registry.js +545 -0
  11. package/dist/tools/build-fact-registry.js.map +1 -0
  12. package/dist/tools/eval-ai-chat.js +34 -4
  13. package/dist/tools/eval-ai-chat.js.map +1 -1
  14. package/dist/tools/lib/audio-processor.d.ts +46 -0
  15. package/dist/tools/lib/audio-processor.d.ts.map +1 -0
  16. package/dist/tools/lib/audio-processor.js +188 -0
  17. package/dist/tools/lib/audio-processor.js.map +1 -0
  18. package/dist/tools/lib/script-generator.d.ts +61 -0
  19. package/dist/tools/lib/script-generator.d.ts.map +1 -0
  20. package/dist/tools/lib/script-generator.js +182 -0
  21. package/dist/tools/lib/script-generator.js.map +1 -0
  22. package/dist/tools/lib/tts-provider.d.ts +65 -0
  23. package/dist/tools/lib/tts-provider.d.ts.map +1 -0
  24. package/dist/tools/lib/tts-provider.js +116 -0
  25. package/dist/tools/lib/tts-provider.js.map +1 -0
  26. package/dist/tools/lib/types.d.ts +129 -0
  27. package/dist/tools/lib/types.d.ts.map +1 -0
  28. package/dist/tools/lib/types.js +64 -0
  29. package/dist/tools/lib/types.js.map +1 -0
  30. package/dist/tools/podcast-feed.d.ts +6 -0
  31. package/dist/tools/podcast-feed.d.ts.map +1 -0
  32. package/dist/tools/podcast-feed.js +121 -0
  33. package/dist/tools/podcast-feed.js.map +1 -0
  34. package/dist/tools/podcast-generate.d.ts +15 -0
  35. package/dist/tools/podcast-generate.d.ts.map +1 -0
  36. package/dist/tools/podcast-generate.js +318 -0
  37. package/dist/tools/podcast-generate.js.map +1 -0
  38. package/dist/tools/podcast-list.d.ts +6 -0
  39. package/dist/tools/podcast-list.d.ts.map +1 -0
  40. package/dist/tools/podcast-list.js +66 -0
  41. package/dist/tools/podcast-list.js.map +1 -0
  42. package/package.json +3 -2
  43. package/template/.gitignore +7 -0
  44. package/template/datas/fact-registry.json +17 -0
  45. package/template/eslint.config.js +3 -0
  46. package/template/functions/api/chat.ts +6 -1
  47. package/template/package.json +8 -6
  48. package/template/src/config.ts +6 -2
  49. package/template/src/data/blog/zh/alerts-examples.md +86 -0
  50. package/template/src/data/blog/zh/code-examples.md +168 -0
  51. package/template/src/data/blog/zh/markmap-examples.md +103 -0
  52. package/template/src/data/blog/zh/math-examples.md +81 -0
  53. package/template/src/data/blog/zh/mermaid-examples.md +82 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * TTS (Text-to-Speech) provider module for podcast generation.
3
+ * Supports OpenAI TTS API with proxy configuration.
4
+ *
5
+ * @module podcast/tts-provider
6
+ */
7
+ import { fetch, ProxyAgent } from "undici";
8
+ /**
9
+ * Resolve TTS configuration from environment variables.
10
+ * Reads from AI_API_KEY, OPENAI_API_KEY, and AI_BASE_URL.
11
+ * @returns TTS configuration object
12
+ */
13
+ export function resolveTTSConfig() {
14
+ const apiKey = process.env.AI_API_KEY || process.env.OPENAI_API_KEY || "";
15
+ const baseUrl = process.env.AI_BASE_URL || "https://api.openai.com";
16
+ return { provider: "openai", apiKey, baseUrl };
17
+ }
18
+ /**
19
+ * Check if TTS API key is configured.
20
+ * @returns True if API key is available
21
+ */
22
+ export function hasTTSApiKey() {
23
+ return resolveTTSConfig().apiKey.length > 0;
24
+ }
25
+ /**
26
+ * Get proxy dispatcher if HTTPS_PROXY or HTTP_PROXY is set.
27
+ * @returns ProxyAgent if proxy is configured, undefined otherwise
28
+ */
29
+ function getProxyDispatcher() {
30
+ const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
31
+ return proxyUrl ? new ProxyAgent(proxyUrl) : undefined;
32
+ }
33
+ /**
34
+ * Normalize base URL by removing trailing /v1 if present.
35
+ * @param baseUrl - Base URL to normalize
36
+ * @returns Normalized URL without trailing /v1
37
+ */
38
+ function normalizeBaseUrl(baseUrl) {
39
+ return baseUrl.replace(/\/v1\/?$/, "");
40
+ }
41
+ /**
42
+ * Convert text to speech using OpenAI TTS API.
43
+ *
44
+ * @param text - Text to convert to speech (must not be empty)
45
+ * @param options - TTS options including voice, model, and speed
46
+ * @returns Audio data as Uint8Array (MP3 format)
47
+ * @throws Error if text is empty, API key is missing, or API request fails
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const audio = await textToSpeech("你好,世界", { voice: "alloy" });
52
+ * // audio is a Uint8Array containing MP3 data
53
+ * ```
54
+ */
55
+ export async function textToSpeech(text, options) {
56
+ // Validate input
57
+ if (!text.trim()) {
58
+ throw new Error("Text cannot be empty");
59
+ }
60
+ const config = resolveTTSConfig();
61
+ if (!config.apiKey) {
62
+ throw new Error("TTS API Key not configured. Set AI_API_KEY or OPENAI_API_KEY environment variable.");
63
+ }
64
+ const model = options.model || "tts-1";
65
+ const speed = options.speed || 1.0;
66
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
67
+ // Build request
68
+ const dispatcher = getProxyDispatcher();
69
+ const requestBody = {
70
+ model,
71
+ input: text,
72
+ voice: options.voice,
73
+ speed,
74
+ response_format: "mp3",
75
+ };
76
+ try {
77
+ const response = await fetch(`${baseUrl}/v1/audio/speech`, {
78
+ method: "POST",
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ Authorization: `Bearer ${config.apiKey}`,
82
+ },
83
+ body: JSON.stringify(requestBody),
84
+ ...(dispatcher && { dispatcher }),
85
+ });
86
+ if (!response.ok) {
87
+ const errText = await response.text();
88
+ const errMsg = errText.slice(0, 500);
89
+ throw new Error(`TTS API error (${response.status}): ${errMsg}`);
90
+ }
91
+ const arrayBuffer = await response.arrayBuffer();
92
+ return new Uint8Array(arrayBuffer);
93
+ }
94
+ catch (error) {
95
+ // Re-throw if already our error
96
+ if (error instanceof Error && error.message.startsWith("TTS API error")) {
97
+ throw error;
98
+ }
99
+ // Wrap other errors
100
+ throw new Error(`TTS request failed: ${error instanceof Error ? error.message : String(error)}`);
101
+ }
102
+ }
103
+ /**
104
+ * Estimate the cost of TTS generation.
105
+ * OpenAI pricing: $15 per 1M characters for tts-1, $30 per 1M for tts-1-hd
106
+ *
107
+ * @param text - Text to estimate cost for
108
+ * @param model - TTS model to use
109
+ * @returns Estimated cost in USD
110
+ */
111
+ export function estimateTTSCost(text, model = "tts-1") {
112
+ const charCount = text.length;
113
+ const pricePerMillion = model === "tts-1" ? 15 : 30;
114
+ return (charCount / 1_000_000) * pricePerMillion;
115
+ }
116
+ //# sourceMappingURL=tts-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts-provider.js","sourceRoot":"","sources":["../../../src/tools/lib/tts-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Core types and interfaces for the podcast generation system.
3
+ *
4
+ * @module podcast/types
5
+ */
6
+ /**
7
+ * Represents a voice/speaker in the podcast.
8
+ */
9
+ export interface PodcastSpeaker {
10
+ /** Unique identifier: "host" or "guest" */
11
+ id: string;
12
+ /** Display name (e.g., "主持人", "Host") */
13
+ name: string;
14
+ /** Role in the conversation */
15
+ role: "host" | "guest";
16
+ /** OpenAI TTS voice ID */
17
+ voiceId: string;
18
+ }
19
+ /**
20
+ * A single line of dialogue in the podcast script.
21
+ */
22
+ export interface DialogueSegment {
23
+ /** Speaker identifier: "host" or "guest" */
24
+ speaker: string;
25
+ /** The spoken text content */
26
+ text: string;
27
+ }
28
+ /**
29
+ * Complete podcast script for one blog article.
30
+ */
31
+ export interface PodcastScript {
32
+ /** Article slug (e.g., "zh/getting-started") */
33
+ slug: string;
34
+ /** Article title */
35
+ title: string;
36
+ /** Language of the content */
37
+ lang: "zh" | "en";
38
+ /** Speakers used in this podcast */
39
+ speakers: PodcastSpeaker[];
40
+ /** Dialogue segments */
41
+ segments: DialogueSegment[];
42
+ /** ISO timestamp when generated */
43
+ generatedAt: string;
44
+ /** MD5 hash of source content */
45
+ contentHash: string;
46
+ }
47
+ /**
48
+ * Metadata for generated podcast audio file.
49
+ */
50
+ export interface PodcastAudioMeta {
51
+ /** Article slug */
52
+ slug: string;
53
+ /** Language */
54
+ lang: "zh" | "en";
55
+ /** Duration in seconds */
56
+ duration: number;
57
+ /** File size in bytes */
58
+ fileSize: number;
59
+ /** Audio format */
60
+ format: "mp3";
61
+ /** ISO timestamp when generated */
62
+ generatedAt: string;
63
+ /** MD5 hash of source content */
64
+ contentHash: string;
65
+ }
66
+ /**
67
+ * Optional frontmatter configuration for podcast generation.
68
+ * Can be added to blog post frontmatter to customize podcast behavior.
69
+ */
70
+ export interface PodcastFrontmatter {
71
+ /** Enable/disable podcast generation for this article */
72
+ enabled?: boolean;
73
+ /** Override default voice assignments */
74
+ voices?: {
75
+ host?: string;
76
+ guest?: string;
77
+ };
78
+ }
79
+ /**
80
+ * Cache structure for podcast scripts.
81
+ */
82
+ export interface PodcastCache {
83
+ meta: {
84
+ lastUpdated: string | null;
85
+ totalProcessed: number;
86
+ };
87
+ articles: Record<string, PodcastScript>;
88
+ }
89
+ /**
90
+ * Audio metadata cache structure.
91
+ */
92
+ export interface PodcastAudioCache {
93
+ meta: {
94
+ lastUpdated: string | null;
95
+ totalProcessed: number;
96
+ };
97
+ articles: Record<string, PodcastAudioMeta>;
98
+ }
99
+ /**
100
+ * Default speaker configurations for each language.
101
+ * Host uses "alloy" (neutral voice), Guest uses "onyx" (deeper voice).
102
+ */
103
+ export declare const DEFAULT_SPEAKERS: Record<"zh" | "en", PodcastSpeaker[]>;
104
+ /**
105
+ * Available OpenAI TTS voices.
106
+ * @see https://platform.openai.com/docs/guides/text-to-speech/voice-options
107
+ */
108
+ export declare const OPENAI_VOICES: readonly ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
109
+ /** OpenAI voice type */
110
+ export type OpenAIVoice = (typeof OPENAI_VOICES)[number];
111
+ /**
112
+ * Type guard to check if a string is a valid OpenAI voice.
113
+ * @param voice - Voice string to check
114
+ * @returns True if the voice is valid
115
+ */
116
+ export declare function isValidOpenAIVoice(voice: string): voice is OpenAIVoice;
117
+ /**
118
+ * TTS models available from OpenAI.
119
+ */
120
+ export declare const OPENAI_TTS_MODELS: readonly ["tts-1", "tts-1-hd"];
121
+ /** OpenAI TTS model type */
122
+ export type OpenAITTSModel = (typeof OPENAI_TTS_MODELS)[number];
123
+ /**
124
+ * Parse podcast frontmatter from a record (parsed YAML frontmatter).
125
+ * @param frontmatter - Parsed frontmatter object
126
+ * @returns PodcastFrontmatter or undefined
127
+ */
128
+ export declare function parsePodcastFrontmatter(frontmatter: Record<string, unknown>): PodcastFrontmatter | undefined;
129
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/lib/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Core types and interfaces for the podcast generation system.
3
+ *
4
+ * @module podcast/types
5
+ */
6
+ /**
7
+ * Default speaker configurations for each language.
8
+ * Host uses "alloy" (neutral voice), Guest uses "onyx" (deeper voice).
9
+ */
10
+ export const DEFAULT_SPEAKERS = {
11
+ zh: [
12
+ { id: "host", name: "主持人", role: "host", voiceId: "alloy" },
13
+ { id: "guest", name: "嘉宾", role: "guest", voiceId: "onyx" },
14
+ ],
15
+ en: [
16
+ { id: "host", name: "Host", role: "host", voiceId: "alloy" },
17
+ { id: "guest", name: "Guest", role: "guest", voiceId: "echo" },
18
+ ],
19
+ };
20
+ /**
21
+ * Available OpenAI TTS voices.
22
+ * @see https://platform.openai.com/docs/guides/text-to-speech/voice-options
23
+ */
24
+ export const OPENAI_VOICES = [
25
+ "alloy",
26
+ "echo",
27
+ "fable",
28
+ "onyx",
29
+ "nova",
30
+ "shimmer",
31
+ ];
32
+ /**
33
+ * Type guard to check if a string is a valid OpenAI voice.
34
+ * @param voice - Voice string to check
35
+ * @returns True if the voice is valid
36
+ */
37
+ export function isValidOpenAIVoice(voice) {
38
+ return OPENAI_VOICES.includes(voice);
39
+ }
40
+ /**
41
+ * TTS models available from OpenAI.
42
+ */
43
+ export const OPENAI_TTS_MODELS = ["tts-1", "tts-1-hd"];
44
+ /**
45
+ * Parse podcast frontmatter from a record (parsed YAML frontmatter).
46
+ * @param frontmatter - Parsed frontmatter object
47
+ * @returns PodcastFrontmatter or undefined
48
+ */
49
+ export function parsePodcastFrontmatter(frontmatter) {
50
+ if (!frontmatter.podcast)
51
+ return undefined;
52
+ const podcast = frontmatter.podcast;
53
+ const voices = podcast.voices;
54
+ return {
55
+ enabled: typeof podcast.enabled === "boolean" ? podcast.enabled : undefined,
56
+ voices: voices
57
+ ? {
58
+ host: typeof voices.host === "string" ? voices.host : undefined,
59
+ guest: typeof voices.guest === "string" ? voices.guest : undefined,
60
+ }
61
+ : undefined,
62
+ };
63
+ }
64
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/tools/lib/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAsGH;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA0C;IACrE,EAAE,EAAE;QACF,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE;QAC3D,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE;KAC5D;IACD,EAAE,EAAE;QACF,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE;QAC5D,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE;KAC/D;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,SAAS;CACD,CAAC;AAKX;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,aAAa,CAAC,QAAQ,CAAC,KAAoB,CAAC,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAC;AAKhE;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,WAAoC;IAEpC,IAAI,CAAC,WAAW,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE3C,MAAM,OAAO,GAAG,WAAW,CAAC,OAAkC,CAAC;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,MAA6C,CAAC;IAErE,OAAO;QACL,OAAO,EACL,OAAO,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QACpE,MAAM,EAAE,MAAM;YACZ,CAAC,CAAC;gBACE,IAAI,EAAE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;gBAC/D,KAAK,EAAE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;aACnE;YACH,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Generate Podcast RSS Feed
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=podcast-feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"podcast-feed.d.ts","sourceRoot":"","sources":["../../src/tools/podcast-feed.ts"],"names":[],"mappings":";AACA;;GAEG"}
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Generate Podcast RSS Feed
4
+ */
5
+ import { readFile, writeFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ const DATA_DIR = join(process.cwd(), "datas");
8
+ const PUBLIC_DIR = join(process.cwd(), "public");
9
+ const AUDIO_FILE = join(DATA_DIR, "podcast-audio.json");
10
+ const SCRIPTS_FILE = join(DATA_DIR, "podcast-scripts.json");
11
+ async function loadSiteConfig() {
12
+ try {
13
+ const configPath = join(process.cwd(), "src", "config.ts");
14
+ const raw = await readFile(configPath, "utf-8");
15
+ const websiteMatch = raw.match(/website:\s*["']([^"']+)["']/);
16
+ const titleMatch = raw.match(/title:\s*["']([^"']+)["']/);
17
+ const descMatch = raw.match(/desc:\s*["']([^"']+)["']/);
18
+ const authorMatch = raw.match(/author:\s*["']([^"']+)["']/);
19
+ return {
20
+ website: websiteMatch?.[1] || process.env.SITE_URL || "https://example.com",
21
+ title: titleMatch?.[1] || "Blog Podcast",
22
+ desc: descMatch?.[1] || "AI-generated podcast from blog posts",
23
+ author: authorMatch?.[1] || "Author",
24
+ };
25
+ }
26
+ catch {
27
+ return {
28
+ website: process.env.SITE_URL || "https://example.com",
29
+ title: "Blog Podcast",
30
+ desc: "AI-generated podcast",
31
+ author: "Author",
32
+ };
33
+ }
34
+ }
35
+ function escapeXml(str) {
36
+ return str
37
+ .replace(/&/g, "&amp;")
38
+ .replace(/</g, "&lt;")
39
+ .replace(/>/g, "&gt;")
40
+ .replace(/"/g, "&quot;")
41
+ .replace(/'/g, "&apos;");
42
+ }
43
+ function generateRss(site, episodes) {
44
+ const items = episodes
45
+ .map((ep) => {
46
+ const audioUrl = `${site.website}/podcasts/${ep.lang}/${ep.slug.split("/")[1]}.mp3`;
47
+ return `
48
+ <item>
49
+ <title>${escapeXml(ep.title)}</title>
50
+ <description>AI-generated podcast episode from blog post: ${escapeXml(ep.title)}</description>
51
+ <enclosure url="${audioUrl}" length="${ep.fileSize}" type="audio/mpeg"/>
52
+ <guid isPermaLink="true">${audioUrl}</guid>
53
+ <pubDate>${new Date(ep.generatedAt).toUTCString()}</pubDate>
54
+ <itunes:duration>${Math.ceil(ep.duration)}</itunes:duration>
55
+ </item>`;
56
+ })
57
+ .join("");
58
+ return `<?xml version="1.0" encoding="UTF-8"?>
59
+ <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:atom="http://www.w3.org/2005/Atom">
60
+ <channel>
61
+ <title>${escapeXml(site.title)} Podcast</title>
62
+ <description>${escapeXml(site.desc)}</description>
63
+ <link>${site.website}</link>
64
+ <atom:link href="${site.website}/podcast.xml" rel="self" type="application/rss+xml"/>
65
+ <language>zh-cn</language>
66
+ <itunes:author>${escapeXml(site.author)}</itunes:author>
67
+ <itunes:summary>${escapeXml(site.desc)}</itunes:summary>
68
+ <itunes:category text="Technology"/>
69
+ <itunes:explicit>false</itunes:explicit>
70
+ ${items}
71
+ </channel>
72
+ </rss>`;
73
+ }
74
+ async function main() {
75
+ const site = await loadSiteConfig();
76
+ let audioData = {
77
+ articles: {},
78
+ };
79
+ try {
80
+ const raw = await readFile(AUDIO_FILE, "utf-8");
81
+ audioData = JSON.parse(raw);
82
+ }
83
+ catch {
84
+ console.log("No audio data found. Run 'astro-minimax podcast generate' first.");
85
+ return;
86
+ }
87
+ let scriptsData = {
88
+ articles: {},
89
+ };
90
+ try {
91
+ const raw = await readFile(SCRIPTS_FILE, "utf-8");
92
+ scriptsData = JSON.parse(raw);
93
+ }
94
+ catch { }
95
+ const episodes = [];
96
+ for (const [slug, meta] of Object.entries(audioData.articles)) {
97
+ const script = scriptsData.articles[slug];
98
+ episodes.push({
99
+ slug,
100
+ title: script?.title || slug.split("/")[1] || slug,
101
+ lang: meta.lang,
102
+ duration: meta.duration,
103
+ fileSize: meta.fileSize,
104
+ generatedAt: meta.generatedAt,
105
+ });
106
+ }
107
+ episodes.sort((a, b) => new Date(b.generatedAt).getTime() - new Date(a.generatedAt).getTime());
108
+ if (episodes.length === 0) {
109
+ console.log("No episodes found.");
110
+ return;
111
+ }
112
+ const rss = generateRss(site, episodes);
113
+ const outputPath = join(PUBLIC_DIR, "podcast.xml");
114
+ await writeFile(outputPath, rss, "utf-8");
115
+ console.log("Generated podcast RSS feed");
116
+ console.log(` Path: ${outputPath}`);
117
+ console.log(` Episodes: ${episodes.length}`);
118
+ console.log(` URL: ${site.website}/podcast.xml`);
119
+ }
120
+ main().catch(console.error);
121
+ //# sourceMappingURL=podcast-feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"podcast-feed.js","sourceRoot":"","sources":["../../src/tools/podcast-feed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Podcast Generation Script
4
+ *
5
+ * Usage:
6
+ * pnpm podcast:generate Generate all podcasts
7
+ * pnpm podcast:generate --slug=zh/xxx Single article
8
+ * pnpm podcast:generate --task=script Script only
9
+ * pnpm podcast:generate --task=audio Audio only
10
+ * pnpm podcast:generate --force Ignore cache
11
+ * pnpm podcast:generate --dry-run Preview only
12
+ * pnpm podcast:generate --lang=zh Specific language
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=podcast-generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"podcast-generate.d.ts","sourceRoot":"","sources":["../../src/tools/podcast-generate.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG"}