@gobing-ai/knowledge-kit 0.0.11 → 0.0.13

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 (99) hide show
  1. package/dist/index.js +451 -152
  2. package/package.json +1 -1
  3. package/plugins/generations/content-gen/dist/index.js +22167 -0
  4. package/plugins/generations/content-gen/plugin.json +1 -1
  5. package/plugins/generations/content-gen/src/storm.ts +99 -50
  6. package/plugins/generations/core-facts-gen/dist/index.js +22048 -0
  7. package/plugins/generations/core-facts-gen/package.json +17 -0
  8. package/plugins/generations/core-facts-gen/plugin.json +7 -0
  9. package/plugins/generations/core-facts-gen/src/index.ts +116 -0
  10. package/plugins/generations/core-facts-gen/tsconfig.json +4 -0
  11. package/plugins/generations/daily-article-gen/dist/index.js +22022 -0
  12. package/plugins/generations/daily-article-gen/package.json +17 -0
  13. package/plugins/generations/daily-article-gen/plugin.json +7 -0
  14. package/plugins/generations/daily-article-gen/src/index.ts +91 -0
  15. package/plugins/generations/daily-article-gen/tsconfig.json +4 -0
  16. package/plugins/generations/dailynews-gen/dist/index.js +22324 -0
  17. package/plugins/generations/dailynews-gen/plugin.json +1 -1
  18. package/plugins/generations/dailynews-gen/src/index.ts +11 -0
  19. package/plugins/generations/dailynews-gen/src/script-builder.ts +1 -1
  20. package/plugins/generations/episode-plan-gen/dist/index.js +22479 -0
  21. package/plugins/generations/episode-plan-gen/package.json +17 -0
  22. package/plugins/generations/episode-plan-gen/plugin.json +7 -0
  23. package/plugins/generations/episode-plan-gen/src/index.ts +726 -0
  24. package/plugins/generations/episode-plan-gen/tsconfig.json +4 -0
  25. package/plugins/generations/image-gen/config.example.yaml +75 -0
  26. package/plugins/generations/image-gen/dist/index.js +22619 -0
  27. package/plugins/generations/image-gen/package.json +17 -0
  28. package/plugins/generations/image-gen/plugin.json +7 -0
  29. package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
  30. package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
  31. package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
  32. package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
  33. package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
  34. package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
  35. package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
  36. package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
  37. package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
  38. package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
  39. package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
  40. package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
  41. package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
  42. package/plugins/generations/image-gen/src/bytes.ts +19 -0
  43. package/plugins/generations/image-gen/src/index.ts +319 -0
  44. package/plugins/generations/image-gen/src/job.ts +143 -0
  45. package/plugins/generations/image-gen/src/paths.ts +31 -0
  46. package/plugins/generations/image-gen/src/presets.ts +344 -0
  47. package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
  48. package/plugins/generations/image-gen/src/providers/types.ts +285 -0
  49. package/plugins/generations/image-gen/tsconfig.json +8 -0
  50. package/plugins/generations/voice-gen/dist/index.js +23027 -0
  51. package/plugins/generations/voice-gen/plugin.json +1 -1
  52. package/plugins/generations/voice-gen/src/index.ts +102 -11
  53. package/plugins/generations/voice-gen/src/qc.ts +154 -9
  54. package/plugins/ingestions/aihot-ingest/dist/index.js +22358 -0
  55. package/plugins/ingestions/aihot-ingest/plugin.json +2 -2
  56. package/plugins/ingestions/aihot-ingest/src/index.ts +72 -13
  57. package/plugins/ingestions/aihot-ingest/src/mapper.ts +1 -0
  58. package/plugins/ingestions/aihot-ingest/src/rss.ts +151 -0
  59. package/plugins/ingestions/horizon-ingest/dist/index.js +22105 -0
  60. package/plugins/ingestions/horizon-ingest/package.json +17 -0
  61. package/plugins/ingestions/horizon-ingest/plugin.json +7 -0
  62. package/plugins/ingestions/horizon-ingest/src/index.ts +205 -0
  63. package/plugins/ingestions/horizon-ingest/tsconfig.json +4 -0
  64. package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
  65. package/plugins/ingestions/karakeep-local/plugin.json +1 -1
  66. package/plugins/ingestions/last30days-ingest/dist/index.js +22050 -0
  67. package/plugins/ingestions/last30days-ingest/package.json +17 -0
  68. package/plugins/ingestions/last30days-ingest/plugin.json +7 -0
  69. package/plugins/ingestions/last30days-ingest/src/index.ts +148 -0
  70. package/plugins/ingestions/last30days-ingest/tsconfig.json +4 -0
  71. package/plugins/ingestions/web-search/dist/index.js +24379 -0
  72. package/plugins/ingestions/web-search/plugin.json +1 -1
  73. package/plugins/kk/commands/image-generate.md +15 -0
  74. package/plugins/kk/config.example.yaml +80 -0
  75. package/plugins/kk/skills/image-authoring/SKILL.md +250 -0
  76. package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
  77. package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
  78. package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
  79. package/plugins/kk/skills/image-authoring/references/style-extraction.md +134 -0
  80. package/plugins/kk/skills/taste-unslop/SKILL.md +12 -6
  81. package/plugins/kk/skills/taste-unslop/references/pattern-guide.md +128 -48
  82. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +432 -19
  83. package/plugins/publishings/emdash-pub/dist/index.js +22243 -0
  84. package/plugins/publishings/emdash-pub/plugin.json +1 -1
  85. package/plugins/publishings/podcast-pub/dist/index.js +22596 -0
  86. package/plugins/publishings/podcast-pub/package.json +17 -0
  87. package/plugins/publishings/podcast-pub/plugin.json +13 -0
  88. package/plugins/publishings/podcast-pub/src/index.ts +538 -0
  89. package/plugins/publishings/podcast-pub/src/map.ts +165 -0
  90. package/plugins/publishings/podcast-pub/src/microfeed-client.ts +196 -0
  91. package/plugins/publishings/podcast-pub/src/show-notes.ts +132 -0
  92. package/plugins/publishings/podcast-pub/tsconfig.json +4 -0
  93. package/plugins/publishings/qiita-pub/dist/index.js +22081 -0
  94. package/plugins/publishings/qiita-pub/plugin.json +1 -1
  95. package/plugins/publishings/surfdash-pub/dist/index.js +22233 -0
  96. package/plugins/publishings/surfdash-pub/plugin.json +1 -1
  97. package/plugins/publishings/surfdash-pub/src/index.ts +328 -62
  98. package/plugins/publishings/zenn-pub/dist/index.js +22122 -0
  99. package/plugins/publishings/zenn-pub/plugin.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aihot-ingest",
3
3
  "kind": "ingestion",
4
- "entry": "./src/index.ts",
4
+ "entry": "./dist/index.js",
5
5
  "version": "1.0.0",
6
- "description": "Ingests curated AI news items from aihot.virxact.com into Doc[]"
6
+ "description": "Ingests fresh AI news from an RSS feed (default TechCrunch AI) into Doc[]; legacy aihot.virxact.com API behind AIHOT_SOURCE=api"
7
7
  }
@@ -6,9 +6,11 @@ import { echoError } from '@gobing-ai/ts-utils';
6
6
  import { z } from 'zod';
7
7
  import { type AihotDeps, createAihotClient } from './client';
8
8
  import { mapAihotItemsToDocs } from './mapper';
9
+ import { AIHOT_FEED_URL_DEFAULT, fetchRssDocs, type RssFetchLike } from './rss';
9
10
 
10
11
  export * from './client';
11
12
  export * from './mapper';
13
+ export * from './rss';
12
14
 
13
15
  export interface IngestionOptions {
14
16
  out: string;
@@ -38,8 +40,26 @@ function parseLimit(value: string): number {
38
40
  export async function processIngestionIO(
39
41
  options: IngestionOptions,
40
42
  depsOverride?: Partial<AihotDeps>,
43
+ rssFetchLike: RssFetchLike = fetch,
41
44
  ): Promise<IngestionResult> {
42
45
  const fs = createNodeFileSystem();
46
+
47
+ // RSS is the default source (2026-09-08 dogfood: the aihot.virxact.com API served a
48
+ // 2020–2025 archive — every item failed the 7-day freshness gate; see rss.ts). The API
49
+ // path stays reachable with AIHOT_SOURCE=api for when the CMS carries live curated items.
50
+ const source = process.env.AIHOT_SOURCE?.trim() || 'rss';
51
+ if (source === 'api') {
52
+ return processApiSource(options, fs, depsOverride);
53
+ }
54
+ return processRssSource(options, fs, rssFetchLike);
55
+ }
56
+
57
+ /** Legacy aihot.virxact.com API source (cursor/stateFile contract preserved). */
58
+ async function processApiSource(
59
+ options: IngestionOptions,
60
+ fs: ReturnType<typeof createNodeFileSystem>,
61
+ depsOverride?: Partial<AihotDeps>,
62
+ ): Promise<IngestionResult> {
43
63
  const client = createAihotClient(depsOverride);
44
64
 
45
65
  let effectiveCursor = options.cursor?.trim();
@@ -68,30 +88,69 @@ export async function processIngestionIO(
68
88
  }
69
89
 
70
90
  const docs = DocListSchema.parse(mapAihotItemsToDocs(items));
71
-
72
- const outDir = dirname(options.out);
73
- if (outDir && outDir !== '.') {
74
- await fs.ensureDir(outDir);
75
- }
76
- await atomicWriteJson(options.out, docs, fs);
91
+ await writeOutput(fs, options, docs);
77
92
 
78
93
  if (options.stateFile) {
79
- const stateDir = dirname(options.stateFile);
80
- if (stateDir && stateDir !== '.') {
81
- await fs.ensureDir(stateDir);
82
- }
83
- const statePayload = {
94
+ await writeState(fs, options.stateFile, {
84
95
  cursor,
85
96
  asOf,
86
97
  lastSyncAt: new Date().toISOString(),
87
98
  itemCount: docs.length,
88
- };
89
- await atomicWriteJson(options.stateFile, statePayload, fs);
99
+ });
90
100
  }
91
101
 
92
102
  return { docs, cursor, asOf };
93
103
  }
94
104
 
105
+ /** RSS feed source (default): stateless fetch, no cursor (feed order is newest-first). */
106
+ async function processRssSource(
107
+ options: IngestionOptions,
108
+ fs: ReturnType<typeof createNodeFileSystem>,
109
+ rssFetchLike: RssFetchLike,
110
+ ): Promise<IngestionResult> {
111
+ const feedUrl = process.env.AIHOT_FEED_URL?.trim() || AIHOT_FEED_URL_DEFAULT;
112
+ const limit = options.limit ?? 10;
113
+ const { docs, asOf } = await fetchRssDocs(feedUrl, limit, rssFetchLike);
114
+ const validated = DocListSchema.parse(docs);
115
+ await writeOutput(fs, options, validated);
116
+
117
+ if (options.stateFile) {
118
+ await writeState(fs, options.stateFile, {
119
+ source: 'rss',
120
+ feedUrl,
121
+ asOf,
122
+ lastSyncAt: new Date().toISOString(),
123
+ itemCount: validated.length,
124
+ });
125
+ }
126
+
127
+ return { docs: validated, cursor: '', asOf };
128
+ }
129
+
130
+ async function writeOutput(
131
+ fs: ReturnType<typeof createNodeFileSystem>,
132
+ options: IngestionOptions,
133
+ docs: Doc[],
134
+ ): Promise<void> {
135
+ const outDir = dirname(options.out);
136
+ if (outDir && outDir !== '.') {
137
+ await fs.ensureDir(outDir);
138
+ }
139
+ await atomicWriteJson(options.out, docs, fs);
140
+ }
141
+
142
+ async function writeState(
143
+ fs: ReturnType<typeof createNodeFileSystem>,
144
+ stateFile: string,
145
+ payload: Record<string, unknown>,
146
+ ): Promise<void> {
147
+ const stateDir = dirname(stateFile);
148
+ if (stateDir && stateDir !== '.') {
149
+ await fs.ensureDir(stateDir);
150
+ }
151
+ await atomicWriteJson(stateFile, payload, fs);
152
+ }
153
+
95
154
  export async function main(depsOverride?: Partial<AihotDeps>): Promise<number> {
96
155
  let values: { in?: string; out?: string; limit?: string; cursor?: string; 'state-file'?: string };
97
156
  try {
@@ -16,6 +16,7 @@ export function mapAihotItemToDoc(item: AihotItem): Doc {
16
16
  const body = `${item.summary?.trim() || item.title.trim()}\n`;
17
17
 
18
18
  const metadata: Record<string, unknown> = {
19
+ source: 'aihot',
19
20
  sourceName: item.source.name,
20
21
  publishedAt: item.publishedAt,
21
22
  discoveredAt: item.discoveredAt,
@@ -0,0 +1,151 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type { Doc } from '@gobing-ai/kk-core';
3
+
4
+ /**
5
+ * RSS/Atom feed fetch + item → Doc mapping for the aihot-ingest RSS source.
6
+ *
7
+ * Why RSS (2026-09-08, dogfood): aihot.virxact.com's API served a 2020–2025 blog
8
+ * archive — all 10 items were dropped every run by the workflow's 7-day freshness
9
+ * gate, so the "curated" aihot source contributed zero usable items while carrying
10
+ * the highest selection tier (SOURCE_BASE aihot=30, importance 4). The RSS source
11
+ * restores aihot as a genuinely fresh feed; the API path stays selectable via
12
+ * AIHOT_SOURCE=api for when the CMS has live curated content.
13
+ *
14
+ * Keeps metadata.source='aihot' so planEpisode's aihot tier (and importance base 4)
15
+ * still applies to feed items.
16
+ */
17
+
18
+ /** Default fresh AI-news feed (RSS 2.0, high volume, reachable without auth). */
19
+ export const AIHOT_FEED_URL_DEFAULT = 'https://techcrunch.com/category/artificial-intelligence/feed/';
20
+
21
+ export type RssFetchLike = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
22
+
23
+ export interface RssEntry {
24
+ title: string;
25
+ link: string;
26
+ publishedAt: string | null; // ISO-8601 or null
27
+ summary: string;
28
+ category: string | null;
29
+ }
30
+
31
+ /** Strip XML/HTML tags and normalize common entities to plain text. */
32
+ function stripMarkup(raw: string): string {
33
+ return raw
34
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
35
+ .replace(/<[^>]+>/g, ' ')
36
+ .replace(/&nbsp;/g, ' ')
37
+ .replace(/&amp;/g, '&')
38
+ .replace(/&lt;/g, '<')
39
+ .replace(/&gt;/g, '>')
40
+ .replace(/&quot;/g, '"')
41
+ .replace(/&#39;|&apos;/g, "'")
42
+ .replace(/\s+/g, ' ')
43
+ .trim();
44
+ }
45
+
46
+ /** RSS <pubDate> (RFC 822) or Atom <published>/<updated> (ISO-8601) → ISO-8601. */
47
+ function toIsoDate(raw: string | undefined): string | null {
48
+ if (!raw?.trim()) return null;
49
+ const t = Date.parse(raw.trim().replace('UT', 'GMT'));
50
+ return Number.isFinite(t) ? new Date(t).toISOString() : null;
51
+ }
52
+
53
+ /** Extract a single sub-element's text from an element body. */
54
+ function subElement(body: string, tag: string): string | undefined {
55
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i');
56
+ const m = body.match(re);
57
+ return m?.[1];
58
+ }
59
+
60
+ /** Extract a link: RSS <link>text</link> or Atom <link href="..."/> (first match wins). */
61
+ function subLink(body: string): string | undefined {
62
+ const text = subElement(body, 'link');
63
+ if (text?.trim()) return text.trim();
64
+ const m = body.match(/<link[^>]*\bhref\s*=\s*"([^"]+)"/i);
65
+ return m?.[1];
66
+ }
67
+
68
+ /** Block-split RSS <item>… or Atom <entry>… elements and map each to an RssEntry. */
69
+ export function parseFeed(xml: string): RssEntry[] {
70
+ const blocks: string[] = [];
71
+ const itemRe = /<item[\s>][\s\S]*?<\/item>/gi;
72
+ const entryRe = /<entry[\s>][\s\S]*?<\/entry>/gi;
73
+ let m = itemRe.exec(xml);
74
+ while (m !== null) {
75
+ blocks.push(m[0]);
76
+ m = itemRe.exec(xml);
77
+ }
78
+ if (blocks.length === 0) {
79
+ m = entryRe.exec(xml);
80
+ while (m !== null) {
81
+ blocks.push(m[0]);
82
+ m = entryRe.exec(xml);
83
+ }
84
+ }
85
+ return blocks.map((b) => {
86
+ const title = stripMarkup(subElement(b, 'title') ?? '');
87
+ const link = subLink(b) ?? '';
88
+ const category = stripMarkup(subElement(b, 'category') ?? '') || null;
89
+ const pubRaw = subElement(b, 'pubDate') ?? subElement(b, 'published') ?? subElement(b, 'updated');
90
+ const summary = stripMarkup(
91
+ subElement(b, 'content:encoded') ?? subElement(b, 'description') ?? subElement(b, 'summary') ?? title,
92
+ );
93
+ return { title, link, publishedAt: toIsoDate(pubRaw), summary, category };
94
+ });
95
+ }
96
+
97
+ export function computeRssDocId(seed: string): string {
98
+ return createHash('sha256').update(seed).digest('hex').slice(0, 16);
99
+ }
100
+
101
+ /** Map a feed entry to the kk Doc shape; source:'aihot' keeps the aihot plan tier. */
102
+ export function mapRssEntryToDoc(entry: RssEntry, feedUrl: string): Doc {
103
+ const id = entry.link ? computeRssDocId(entry.link) : computeRssDocId(`${entry.title}${entry.summary}`);
104
+ let sourceName = 'aihot-feed';
105
+ try {
106
+ sourceName = new URL(feedUrl).hostname;
107
+ } catch {
108
+ // keep 'aihot-feed' fallback
109
+ }
110
+ return {
111
+ id,
112
+ title: entry.title || 'Untitled AI News',
113
+ sourceUri: entry.link || feedUrl,
114
+ body: `${entry.title}\n\n${entry.summary}\n`,
115
+ mediaType: 'text/markdown',
116
+ metadata: {
117
+ source: 'aihot',
118
+ sourceName,
119
+ publishedAt: entry.publishedAt ?? undefined,
120
+ discoveredAt: new Date().toISOString(),
121
+ originalUrl: entry.link || undefined,
122
+ category: entry.category ?? undefined,
123
+ },
124
+ };
125
+ }
126
+
127
+ export async function fetchRssDocs(
128
+ feedUrl: string,
129
+ limit: number,
130
+ fetchLike: RssFetchLike = fetch,
131
+ ): Promise<{ docs: Doc[]; asOf: string }> {
132
+ const res = await fetchLike(feedUrl, {
133
+ headers: { 'user-agent': 'aihot-ingest/1.0.0 (RSS source)' },
134
+ });
135
+ if (!res.ok) {
136
+ throw new Error(`aihot-ingest RSS fetch failed: HTTP ${res.status} from ${feedUrl}`);
137
+ }
138
+ const xml = await res.text();
139
+ const entries = parseFeed(xml);
140
+ // Feed order is newest-first for RSS; atom may vary — prefer publication order.
141
+ const seen = new Set<string>();
142
+ const docs: Doc[] = [];
143
+ for (const entry of entries) {
144
+ if (docs.length >= limit) break;
145
+ const key = entry.link || entry.title;
146
+ if (seen.has(key)) continue;
147
+ seen.add(key);
148
+ docs.push(mapRssEntryToDoc(entry, feedUrl));
149
+ }
150
+ return { docs, asOf: new Date().toISOString() };
151
+ }