kimi-agent-swarm-cli 0.7.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.
@@ -0,0 +1,262 @@
1
+ {
2
+ "sources": [
3
+ {
4
+ "id": "YT-NICHE-001",
5
+ "url": "https://example.com/youtube-niche/personal-finance-for-gen-z",
6
+ "title": "Personal Finance for Gen Z",
7
+ "sourceClass": "primary-analysis",
8
+ "publishedAt": "2026-05-20",
9
+ "discoveredBy": "youtube-niche-fixture",
10
+ "scores": {
11
+ "relevance": 5,
12
+ "authority": 4,
13
+ "freshness": 4,
14
+ "diversity": 3,
15
+ "extractionValue": 5
16
+ },
17
+ "claims": [
18
+ "Niche: Personal finance for Gen Z and young millennials.",
19
+ "Audience: 18-28 year olds entering the workforce with student debt and first salaries.",
20
+ "Repeated pain point: Confusion about investing, budgeting apps, and credit building.",
21
+ "Evidence signal: Multiple channels with 500K+ subscribers and consistent 100K+ views.",
22
+ "Freshness signal: Algorithm and product launches change monthly; evergreen fundamentals remain stable.",
23
+ "Monetization angle: Affiliate links to neobanks, investment apps, budgeting tools, and digital courses.",
24
+ "Caveat: High competition; differentiation requires strong personal story or credentials."
25
+ ]
26
+ },
27
+ {
28
+ "id": "YT-NICHE-002",
29
+ "url": "https://example.com/youtube-niche/slow-living-minimalism",
30
+ "title": "Slow Living and Minimalism",
31
+ "sourceClass": "primary-analysis",
32
+ "publishedAt": "2026-05-18",
33
+ "discoveredBy": "youtube-niche-fixture",
34
+ "scores": {
35
+ "relevance": 4,
36
+ "authority": 4,
37
+ "freshness": 4,
38
+ "diversity": 4,
39
+ "extractionValue": 4
40
+ },
41
+ "claims": [
42
+ "Niche: Slow living, minimalism, and intentional lifestyle design.",
43
+ "Audience: 25-40 year olds overwhelmed by consumer culture and digital overload.",
44
+ "Repeated pain point: Desire to declutter home, calendar, and mental space.",
45
+ "Evidence signal: High engagement on room-tour, declutter-with-me, and capsule-wardrobe videos.",
46
+ "Freshness signal: Trends shift with seasons and sustainability discourse.",
47
+ "Monetization angle: Eco-friendly product affiliates, digital planners, Patreon, and brand partnerships.",
48
+ "Caveat: Audience may resist overt consumerism, so sponsorships must align with values."
49
+ ]
50
+ },
51
+ {
52
+ "id": "YT-NICHE-003",
53
+ "url": "https://example.com/youtube-niche/ai-tools-for-creators",
54
+ "title": "AI Tools for Creators",
55
+ "sourceClass": "primary-analysis",
56
+ "publishedAt": "2026-06-01",
57
+ "discoveredBy": "youtube-niche-fixture",
58
+ "scores": {
59
+ "relevance": 5,
60
+ "authority": 4,
61
+ "freshness": 5,
62
+ "diversity": 3,
63
+ "extractionValue": 5
64
+ },
65
+ "claims": [
66
+ "Niche: Tutorials and reviews of AI tools for writers, designers, and video creators.",
67
+ "Audience: Freelancers, solopreneurs, and content creators looking to save time.",
68
+ "Repeated pain point: Overwhelm from rapidly changing AI tools and unclear ROI.",
69
+ "Evidence signal: Tool-comparison and workflow-automation videos frequently pass 200K views.",
70
+ "Freshness signal: Extremely fast-moving; weekly updates needed to stay relevant.",
71
+ "Monetization angle: Affiliate links to SaaS tools, paid newsletters, and cohort courses.",
72
+ "Caveat: High churn; tool popularity can collapse within months."
73
+ ]
74
+ },
75
+ {
76
+ "id": "YT-NICHE-004",
77
+ "url": "https://example.com/youtube-niche/over-40-fitness",
78
+ "title": "Fitness for Adults Over 40",
79
+ "sourceClass": "primary-analysis",
80
+ "publishedAt": "2026-04-15",
81
+ "discoveredBy": "youtube-niche-fixture",
82
+ "scores": {
83
+ "relevance": 4,
84
+ "authority": 4,
85
+ "freshness": 3,
86
+ "diversity": 3,
87
+ "extractionValue": 4
88
+ },
89
+ "claims": [
90
+ "Niche: Strength, mobility, and nutrition for adults over 40.",
91
+ "Audience: 40-60 year olds concerned about sarcopenia, joint health, and sustainable habits.",
92
+ "Repeated pain point: Fear of injury and confusion about what exercises are safe.",
93
+ "Evidence signal: Strong comments and community posts around form checks and modifications.",
94
+ "Freshness signal: Evergreen with periodic updates as new longevity research emerges.",
95
+ "Monetization angle: Online coaching, supplement affiliates, and age-specific workout programs.",
96
+ "Caveat: Requires credibility; misinformation risks are high in health content."
97
+ ]
98
+ },
99
+ {
100
+ "id": "YT-NICHE-005",
101
+ "url": "https://example.com/youtube-niche/indoor-gardening",
102
+ "title": "Indoor Gardening and Houseplants",
103
+ "sourceClass": "primary-analysis",
104
+ "publishedAt": "2026-05-10",
105
+ "discoveredBy": "youtube-niche-fixture",
106
+ "scores": {
107
+ "relevance": 4,
108
+ "authority": 3,
109
+ "freshness": 4,
110
+ "diversity": 4,
111
+ "extractionValue": 3
112
+ },
113
+ "claims": [
114
+ "Niche: Indoor gardening, houseplant care, and apartment-friendly green spaces.",
115
+ "Audience: Urban renters and homeowners aged 24-45 seeking nature at home.",
116
+ "Repeated pain point: Plants dying due to watering, light, or pest issues.",
117
+ "Evidence signal: High search volume for care guides and troubleshooting videos.",
118
+ "Freshness signal: Seasonal trends around spring planting and holiday gifting.",
119
+ "Monetization angle: Plant shop affiliates, grow-light sponsors, and merchandise.",
120
+ "Caveat: Visual production quality matters; audience expects calming aesthetics."
121
+ ]
122
+ },
123
+ {
124
+ "id": "YT-NICHE-006",
125
+ "url": "https://example.com/youtube-niche/language-learning-short-form",
126
+ "title": "Short-Form Language Learning",
127
+ "sourceClass": "primary-analysis",
128
+ "publishedAt": "2026-05-25",
129
+ "discoveredBy": "youtube-niche-fixture",
130
+ "scores": {
131
+ "relevance": 4,
132
+ "authority": 4,
133
+ "freshness": 5,
134
+ "diversity": 3,
135
+ "extractionValue": 4
136
+ },
137
+ "claims": [
138
+ "Niche: Bite-sized language lessons and pronunciation tips in Shorts/Reels format.",
139
+ "Audience: Busy commuters and casual learners who want daily micro-lessons.",
140
+ "Repeated pain point: Lack of time for traditional language courses.",
141
+ "Evidence signal: Shorts with native-speaker examples get high replay and share rates.",
142
+ "Freshness signal: Algorithm favors consistency and trending audio hooks.",
143
+ "Monetization angle: Language app affiliates, premium courses, and membership communities.",
144
+ "Caveat: Short-form limits depth; retention to long-form courses can be low."
145
+ ]
146
+ },
147
+ {
148
+ "id": "YT-NICHE-007",
149
+ "url": "https://example.com/youtube-niche/career-pivoting",
150
+ "title": "Career Pivoting and Job Search Strategy",
151
+ "sourceClass": "primary-analysis",
152
+ "publishedAt": "2026-05-12",
153
+ "discoveredBy": "youtube-niche-fixture",
154
+ "scores": {
155
+ "relevance": 4,
156
+ "authority": 4,
157
+ "freshness": 4,
158
+ "diversity": 3,
159
+ "extractionValue": 4
160
+ },
161
+ "claims": [
162
+ "Niche: Career change, resume optimization, interview preparation, and salary negotiation.",
163
+ "Audience: Mid-career professionals aged 28-45 considering industry or role changes.",
164
+ "Repeated pain point: Uncertainty about transferable skills and fear of starting over.",
165
+ "Evidence signal: High engagement on resume-roast and mock-interview videos.",
166
+ "Freshness signal: Labor market conditions and hiring tools evolve continuously.",
167
+ "Monetization angle: Resume templates, LinkedIn optimization services, and coaching packages.",
168
+ "Caveat: Outcomes vary heavily by industry and location; avoid overpromising."
169
+ ]
170
+ },
171
+ {
172
+ "id": "YT-NICHE-008",
173
+ "url": "https://example.com/youtube-niche/home-coffee-roasting",
174
+ "title": "Home Coffee Roasting and Brewing",
175
+ "sourceClass": "primary-analysis",
176
+ "publishedAt": "2026-04-28",
177
+ "discoveredBy": "youtube-niche-fixture",
178
+ "scores": {
179
+ "relevance": 3,
180
+ "authority": 3,
181
+ "freshness": 3,
182
+ "diversity": 4,
183
+ "extractionValue": 3
184
+ },
185
+ "claims": [
186
+ "Niche: Home coffee roasting, brewing techniques, and equipment reviews.",
187
+ "Audience: Coffee enthusiasts aged 25-45 willing to invest in gear and beans.",
188
+ "Repeated pain point: Difficulty replicating cafe-quality results at home.",
189
+ "Evidence signal: Active communities around specific roasters, grinders, and brew methods.",
190
+ "Freshness signal: New equipment releases and harvest seasons drive periodic spikes.",
191
+ "Monetization angle: Equipment affiliates, green-bean suppliers, and brewing courses.",
192
+ "Caveat: Niche is smaller; high average revenue per viewer but limited scale."
193
+ ]
194
+ },
195
+ {
196
+ "id": "YT-NICHE-009",
197
+ "url": "https://example.com/youtube-niche/accessible-travel",
198
+ "title": "Accessible and Inclusive Travel",
199
+ "sourceClass": "primary-analysis",
200
+ "publishedAt": "2026-05-05",
201
+ "discoveredBy": "youtube-niche-fixture",
202
+ "scores": {
203
+ "relevance": 4,
204
+ "authority": 3,
205
+ "freshness": 4,
206
+ "diversity": 5,
207
+ "extractionValue": 4
208
+ },
209
+ "claims": [
210
+ "Niche: Travel content focused on accessibility, neurodiversity, and inclusive experiences.",
211
+ "Audience: Travelers with disabilities, families with strollers, and seniors.",
212
+ "Repeated pain point: Lack of reliable accessibility information for destinations.",
213
+ "Evidence signal: Strong community gratitude and low competition for specific destinations.",
214
+ "Freshness signal: Destination guides need updates as venues change.",
215
+ "Monetization angle: Accessible travel agencies, hotel affiliates, and tourism board partnerships.",
216
+ "Caveat: Requires lived experience or deep research to maintain trust."
217
+ ]
218
+ },
219
+ {
220
+ "id": "YT-NICHE-010",
221
+ "url": "https://example.com/youtube-niche/vanlife-europe",
222
+ "title": "Vanlife and Road Travel in Europe",
223
+ "sourceClass": "primary-analysis",
224
+ "publishedAt": "2026-05-22",
225
+ "discoveredBy": "youtube-niche-fixture",
226
+ "scores": {
227
+ "relevance": 3,
228
+ "authority": 3,
229
+ "freshness": 4,
230
+ "diversity": 4,
231
+ "extractionValue": 3
232
+ },
233
+ "claims": [
234
+ "Niche: Vanlife routes, campsites, and logistics for European road travel.",
235
+ "Audience: Remote workers and retirees exploring long-term vehicle-based travel.",
236
+ "Repeated pain point: Complex regulations for camping, insurance, and border crossings.",
237
+ "Evidence signal: Route-planning and budget-breakdown videos perform consistently.",
238
+ "Freshness signal: Seasonal route guides and changing local regulations require updates.",
239
+ "Monetization angle: Van conversion sponsors, campsite apps, travel insurance affiliates.",
240
+ "Caveat: Travel disruptions and fuel costs can quickly date recommendations."
241
+ ]
242
+ },
243
+ {
244
+ "id": "YT-NICHE-LOW-QUALITY",
245
+ "url": "https://example.com/youtube-niche/generic-forum",
246
+ "title": "Generic YouTube Growth Forum Thread",
247
+ "sourceClass": "secondary",
248
+ "publishedAt": "2024-01-01",
249
+ "discoveredBy": "youtube-niche-fixture",
250
+ "scores": {
251
+ "relevance": 1,
252
+ "authority": 1,
253
+ "freshness": 1,
254
+ "diversity": 1,
255
+ "extractionValue": 1
256
+ },
257
+ "claims": [
258
+ "Forum discussions may contain anecdotes but lack structured niche analysis."
259
+ ]
260
+ }
261
+ ]
262
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "kimi-agent-swarm-cli",
3
+ "version": "0.7.0",
4
+ "description": "Evidence-backed wide-search CLI for Kimi Agent Swarm",
5
+ "type": "module",
6
+ "bin": {
7
+ "kasw": "src/cli.ts"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "fixtures",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "bun": ">=1.0.0"
17
+ },
18
+ "scripts": {
19
+ "test": "bun test",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "bun run typecheck && bun test",
22
+ "pack": "npm pack"
23
+ },
24
+ "devDependencies": {
25
+ "@types/bun": "latest",
26
+ "typescript": "^5.8.0"
27
+ },
28
+ "optionalDependencies": {
29
+ "ioredis": "^5.4.0"
30
+ },
31
+ "keywords": [
32
+ "kimi",
33
+ "agent",
34
+ "swarm",
35
+ "wide-search",
36
+ "research",
37
+ "cli"
38
+ ],
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/min9lin9/kimi-agent-swarm-skill.git",
43
+ "directory": "runtime/wide-search"
44
+ }
45
+ }
@@ -0,0 +1,151 @@
1
+ import { join } from "node:path";
2
+
3
+ import { getGitCommit, recordEntry } from "./leaderboard";
4
+ import { runWideSearch } from "./runtime";
5
+ import type { BenchmarkResult, Claim, EnrichedSource, GoldenAnswer } from "./types";
6
+
7
+ function normalizeClaimText(text: string): string {
8
+ return text
9
+ .toLowerCase()
10
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
11
+ .replace(/\s+/g, " ")
12
+ .trim();
13
+ }
14
+
15
+ function tokenSet(text: string): Set<string> {
16
+ return new Set(normalizeClaimText(text).split(" ").filter(Boolean));
17
+ }
18
+
19
+ function jaccardSimilarity(a: string, b: string): number {
20
+ const setA = tokenSet(a);
21
+ const setB = tokenSet(b);
22
+ if (setA.size === 0 && setB.size === 0) return 1;
23
+ const intersection = new Set([...setA].filter((x) => setB.has(x)));
24
+ const union = new Set([...setA, ...setB]);
25
+ return intersection.size / union.size;
26
+ }
27
+
28
+ function findBestMatch(
29
+ expectedClaim: string,
30
+ actualClaims: Claim[],
31
+ threshold = 0.6,
32
+ ): Claim | undefined {
33
+ let best: Claim | undefined;
34
+ let bestScore = 0;
35
+ for (const claim of actualClaims) {
36
+ const score = jaccardSimilarity(expectedClaim, claim.claim);
37
+ if (score > bestScore && score >= threshold) {
38
+ bestScore = score;
39
+ best = claim;
40
+ }
41
+ }
42
+ return best;
43
+ }
44
+
45
+ export async function runBenchmark(
46
+ profile: string,
47
+ golden: GoldenAnswer,
48
+ workDir: string = process.cwd(),
49
+ ): Promise<BenchmarkResult> {
50
+ const objective = `Benchmark: ${profile}`;
51
+ const runResult = await runWideSearch({
52
+ objective,
53
+ profile: profile as "fixture-paul-graham-corpus",
54
+ workDir,
55
+ });
56
+
57
+ const { acceptedSources = 0 } = runResult.verification;
58
+ if (acceptedSources === 0) {
59
+ const failed: BenchmarkResult = {
60
+ profile,
61
+ runId: runResult.runId,
62
+ runDir: runResult.runDir,
63
+ precision: 0,
64
+ recall: 0,
65
+ citationAccuracy: 0,
66
+ f1: 0,
67
+ passed: false,
68
+ };
69
+ await recordEntry({
70
+ runId: failed.runId,
71
+ profile,
72
+ runDir: failed.runDir,
73
+ timestamp: new Date().toISOString(),
74
+ gitCommit: await getGitCommit(),
75
+ scores: failed,
76
+ });
77
+ return failed;
78
+ }
79
+
80
+ // We only have verification summary here, but the actual claims live in the ledger.
81
+ // Re-read ledger files for scoring.
82
+ const ledgerPath = join(runResult.runDir, "claim-ledger.jsonl");
83
+ const sourceLedgerPath = join(runResult.runDir, "source-ledger.jsonl");
84
+
85
+ const claimModule = await import("node:fs/promises");
86
+ const claimText = await claimModule.readFile(ledgerPath, "utf8");
87
+ const sourceText = await claimModule.readFile(sourceLedgerPath, "utf8");
88
+
89
+ const actualClaims: Claim[] = claimText
90
+ .split(/\r?\n/)
91
+ .filter(Boolean)
92
+ .map((line) => JSON.parse(line) as Claim);
93
+
94
+ const sources: EnrichedSource[] = sourceText
95
+ .split(/\r?\n/)
96
+ .filter(Boolean)
97
+ .map((line) => JSON.parse(line) as EnrichedSource);
98
+
99
+ const acceptedClaims = actualClaims;
100
+
101
+ let matchedGolden = 0;
102
+ const matchedActualIds = new Set<string>();
103
+ for (const expectedClaim of golden.expectedClaims) {
104
+ const match = findBestMatch(expectedClaim, acceptedClaims);
105
+ if (match) {
106
+ matchedGolden += 1;
107
+ matchedActualIds.add(match.id);
108
+ }
109
+ }
110
+
111
+ const precision = acceptedClaims.length > 0 ? matchedActualIds.size / acceptedClaims.length : 0;
112
+ const recall = golden.expectedClaims.length > 0 ? matchedGolden / golden.expectedClaims.length : 0;
113
+ const f1 = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0;
114
+
115
+ const citedClaims = acceptedClaims.filter(
116
+ (claim) => Array.isArray(claim.sourceIds) && claim.sourceIds.length > 0,
117
+ );
118
+ const citationAccuracy = acceptedClaims.length > 0 ? citedClaims.length / acceptedClaims.length : 0;
119
+
120
+ // Optional URL coverage check
121
+ if (golden.expectedSourceUrls && golden.expectedSourceUrls.length > 0) {
122
+ const acceptedUrls = new Set(sources.filter((s) => s.decision === "accepted").map((s) => s.url));
123
+ const coveredUrls = golden.expectedSourceUrls.filter((url) => acceptedUrls.has(url));
124
+ if (coveredUrls.length / golden.expectedSourceUrls.length < 0.5) {
125
+ // URL coverage below 50% is a soft failure; does not override numeric scores.
126
+ }
127
+ }
128
+
129
+ const benchmarkResult: BenchmarkResult = {
130
+ profile,
131
+ runId: runResult.runId,
132
+ runDir: runResult.runDir,
133
+ precision: Number(precision.toFixed(4)),
134
+ recall: Number(recall.toFixed(4)),
135
+ citationAccuracy: Number(citationAccuracy.toFixed(4)),
136
+ f1: Number(f1.toFixed(4)),
137
+ passed: recall >= 0.5 && citationAccuracy >= 0.8,
138
+ };
139
+
140
+ const gitCommit = await getGitCommit();
141
+ await recordEntry({
142
+ runId: benchmarkResult.runId,
143
+ profile,
144
+ runDir: benchmarkResult.runDir,
145
+ timestamp: new Date().toISOString(),
146
+ gitCommit,
147
+ scores: benchmarkResult,
148
+ });
149
+
150
+ return benchmarkResult;
151
+ }
package/src/cache.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import type { CacheKey, Source } from "./types";
7
+
8
+ export interface CacheEntry {
9
+ sources: Source[];
10
+ cachedAt: string;
11
+ }
12
+
13
+ export function getCacheDir(): string {
14
+ return join(homedir(), ".kasw", "cache");
15
+ }
16
+
17
+ export function cacheKeyHash(key: CacheKey): string {
18
+ const normalized = JSON.stringify({
19
+ provider: key.provider,
20
+ objective: key.objective.trim().toLowerCase(),
21
+ depth: key.depth,
22
+ maxResults: key.maxResults,
23
+ });
24
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
25
+ }
26
+
27
+ function cacheFilePath(key: CacheKey): string {
28
+ return join(getCacheDir(), `${cacheKeyHash(key)}.json`);
29
+ }
30
+
31
+ export async function getCachedSources(
32
+ key: CacheKey,
33
+ ttlHours = 168, // 7 days default
34
+ ): Promise<Source[] | undefined> {
35
+ const path = cacheFilePath(key);
36
+ try {
37
+ const text = await readFile(path, "utf8");
38
+ const entry = JSON.parse(text) as CacheEntry;
39
+
40
+ const cachedAt = new Date(entry.cachedAt).getTime();
41
+ const now = Date.now();
42
+ const ttlMs = ttlHours * 60 * 60 * 1000;
43
+ if (now - cachedAt > ttlMs) {
44
+ return undefined;
45
+ }
46
+
47
+ return entry.sources;
48
+ } catch (error) {
49
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
50
+ return undefined;
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+
56
+ export async function setCachedSources(key: CacheKey, sources: Source[]): Promise<void> {
57
+ const path = cacheFilePath(key);
58
+ await mkdir(getCacheDir(), { recursive: true });
59
+ const entry: CacheEntry = {
60
+ sources,
61
+ cachedAt: new Date().toISOString(),
62
+ };
63
+ await writeFile(path, `${JSON.stringify(entry, null, 2)}\n`);
64
+ }
65
+
66
+ export async function clearCache(): Promise<number> {
67
+ const dir = getCacheDir();
68
+ let files: string[];
69
+ try {
70
+ files = await readdir(dir);
71
+ } catch (error) {
72
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
73
+ return 0;
74
+ }
75
+ throw error;
76
+ }
77
+
78
+ let removed = 0;
79
+ for (const file of files) {
80
+ if (file.endsWith(".json")) {
81
+ await unlink(join(dir, file));
82
+ removed += 1;
83
+ }
84
+ }
85
+ return removed;
86
+ }