@crowdlisten/harness 1.0.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.
Files changed (109) hide show
  1. package/AGENTS.md +167 -0
  2. package/LICENSE +21 -0
  3. package/README.md +153 -0
  4. package/dist/agent-proxy.d.ts +24 -0
  5. package/dist/agent-proxy.js +140 -0
  6. package/dist/agent-tools.d.ts +736 -0
  7. package/dist/agent-tools.js +409 -0
  8. package/dist/context/api.d.ts +5 -0
  9. package/dist/context/api.js +164 -0
  10. package/dist/context/cli.d.ts +19 -0
  11. package/dist/context/cli.js +108 -0
  12. package/dist/context/extractor.d.ts +12 -0
  13. package/dist/context/extractor.js +43 -0
  14. package/dist/context/index.d.ts +12 -0
  15. package/dist/context/index.js +11 -0
  16. package/dist/context/matcher.d.ts +39 -0
  17. package/dist/context/matcher.js +246 -0
  18. package/dist/context/parser.d.ts +28 -0
  19. package/dist/context/parser.js +157 -0
  20. package/dist/context/pipeline.d.ts +26 -0
  21. package/dist/context/pipeline.js +56 -0
  22. package/dist/context/prompts.d.ts +6 -0
  23. package/dist/context/prompts.js +60 -0
  24. package/dist/context/providers.d.ts +6 -0
  25. package/dist/context/providers.js +106 -0
  26. package/dist/context/redactor.d.ts +10 -0
  27. package/dist/context/redactor.js +68 -0
  28. package/dist/context/server.d.ts +5 -0
  29. package/dist/context/server.js +134 -0
  30. package/dist/context/store.d.ts +12 -0
  31. package/dist/context/store.js +82 -0
  32. package/dist/context/types.d.ts +79 -0
  33. package/dist/context/types.js +4 -0
  34. package/dist/context/user-state.d.ts +40 -0
  35. package/dist/context/user-state.js +144 -0
  36. package/dist/index.d.ts +14 -0
  37. package/dist/index.js +385 -0
  38. package/dist/insights/browser/BrowserPool.d.ts +87 -0
  39. package/dist/insights/browser/BrowserPool.js +266 -0
  40. package/dist/insights/browser/RequestInterceptor.d.ts +46 -0
  41. package/dist/insights/browser/RequestInterceptor.js +115 -0
  42. package/dist/insights/cli.d.ts +8 -0
  43. package/dist/insights/cli.js +206 -0
  44. package/dist/insights/core/base/BaseAdapter.d.ts +37 -0
  45. package/dist/insights/core/base/BaseAdapter.js +123 -0
  46. package/dist/insights/core/health/HealthMonitor.d.ts +75 -0
  47. package/dist/insights/core/health/HealthMonitor.js +171 -0
  48. package/dist/insights/core/interfaces/SocialMediaPlatform.d.ts +125 -0
  49. package/dist/insights/core/interfaces/SocialMediaPlatform.js +42 -0
  50. package/dist/insights/core/utils/DataNormalizer.d.ts +53 -0
  51. package/dist/insights/core/utils/DataNormalizer.js +349 -0
  52. package/dist/insights/core/utils/InstagramUrlUtils.d.ts +11 -0
  53. package/dist/insights/core/utils/InstagramUrlUtils.js +60 -0
  54. package/dist/insights/core/utils/TikTokUrlUtils.d.ts +10 -0
  55. package/dist/insights/core/utils/TikTokUrlUtils.js +57 -0
  56. package/dist/insights/handlers.d.ts +157 -0
  57. package/dist/insights/handlers.js +246 -0
  58. package/dist/insights/index.d.ts +437 -0
  59. package/dist/insights/index.js +426 -0
  60. package/dist/insights/platforms/instagram/InstagramAdapter.d.ts +34 -0
  61. package/dist/insights/platforms/instagram/InstagramAdapter.js +342 -0
  62. package/dist/insights/platforms/moltbook/MoltbookAdapter.d.ts +31 -0
  63. package/dist/insights/platforms/moltbook/MoltbookAdapter.js +227 -0
  64. package/dist/insights/platforms/reddit/RedditAdapter.d.ts +21 -0
  65. package/dist/insights/platforms/reddit/RedditAdapter.js +212 -0
  66. package/dist/insights/platforms/tiktok/TikTokAdapter.d.ts +34 -0
  67. package/dist/insights/platforms/tiktok/TikTokAdapter.js +269 -0
  68. package/dist/insights/platforms/twitter/TwitterAdapter.d.ts +23 -0
  69. package/dist/insights/platforms/twitter/TwitterAdapter.js +211 -0
  70. package/dist/insights/platforms/xiaohongshu/XiaohongshuAdapter.d.ts +35 -0
  71. package/dist/insights/platforms/xiaohongshu/XiaohongshuAdapter.js +258 -0
  72. package/dist/insights/platforms/youtube/YouTubeAdapter.d.ts +22 -0
  73. package/dist/insights/platforms/youtube/YouTubeAdapter.js +254 -0
  74. package/dist/insights/service-config.d.ts +7 -0
  75. package/dist/insights/service-config.js +60 -0
  76. package/dist/insights/services/UnifiedSocialMediaService.d.ts +94 -0
  77. package/dist/insights/services/UnifiedSocialMediaService.js +259 -0
  78. package/dist/insights/vision/VisionExtractor.d.ts +46 -0
  79. package/dist/insights/vision/VisionExtractor.js +236 -0
  80. package/dist/learnings.d.ts +50 -0
  81. package/dist/learnings.js +130 -0
  82. package/dist/openapi.d.ts +29 -0
  83. package/dist/openapi.js +169 -0
  84. package/dist/server-factory.d.ts +20 -0
  85. package/dist/server-factory.js +41 -0
  86. package/dist/suggestions.d.ts +16 -0
  87. package/dist/suggestions.js +72 -0
  88. package/dist/telemetry.d.ts +44 -0
  89. package/dist/telemetry.js +93 -0
  90. package/dist/tools/registry.d.ts +65 -0
  91. package/dist/tools/registry.js +256 -0
  92. package/dist/tools.d.ts +2433 -0
  93. package/dist/tools.js +2294 -0
  94. package/dist/transport/http.d.ts +15 -0
  95. package/dist/transport/http.js +154 -0
  96. package/package.json +76 -0
  97. package/skills/catalog.json +272 -0
  98. package/skills/community-catalog.json +4202 -0
  99. package/skills/competitive-analysis/SKILL.md +174 -0
  100. package/skills/content-creator/SKILL.md +256 -0
  101. package/skills/content-strategy/SKILL.md +222 -0
  102. package/skills/data-storytelling/SKILL.md +248 -0
  103. package/skills/heuristic-evaluation/SKILL.md +201 -0
  104. package/skills/market-research-reports/SKILL.md +184 -0
  105. package/skills/user-stories/SKILL.md +178 -0
  106. package/skills/ux-researcher/SKILL.md +239 -0
  107. package/web-dist/assets/index-B1b25lNd.css +1 -0
  108. package/web-dist/assets/index-CDWHwHbl.js +64 -0
  109. package/web-dist/index.html +16 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * MCP Server Factory — shared between stdio and HTTP transports
3
+ *
4
+ * Creates a configured MCP Server instance with tool handlers.
5
+ * Both stdio (CLI) and HTTP (remote) transports use this factory
6
+ * so the tool surface is identical regardless of transport.
7
+ */
8
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
9
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
10
+ import { handleTool, TOOLS } from "./tools.js";
11
+ const SERVER_NAME = "crowdlisten/harness";
12
+ const SERVER_VERSION = "0.6.0";
13
+ /**
14
+ * Creates and configures an MCP Server with all tool handlers registered.
15
+ * The caller is responsible for connecting a transport (stdio or HTTP).
16
+ */
17
+ export function createMcpServer(opts) {
18
+ const { supabase: sb, userId } = opts;
19
+ const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
20
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
21
+ tools: TOOLS,
22
+ }));
23
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
24
+ const { name, arguments: toolArgs } = request.params;
25
+ try {
26
+ const result = await handleTool(sb, userId, name, (toolArgs || {}));
27
+ return { content: [{ type: "text", text: result }] };
28
+ }
29
+ catch (err) {
30
+ const message = err instanceof Error ? err.message : String(err);
31
+ return {
32
+ content: [
33
+ { type: "text", text: JSON.stringify({ error: message }) },
34
+ ],
35
+ isError: true,
36
+ };
37
+ }
38
+ });
39
+ return server;
40
+ }
41
+ export { SERVER_NAME, SERVER_VERSION };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Proactive Skill Pack Suggestions — keyword heuristics on tool results.
3
+ *
4
+ * After each tool call, scans the result text for keywords that map to
5
+ * inactive packs. Suggests activation with cooldown to avoid noise.
6
+ */
7
+ export interface Suggestion {
8
+ pack_id: string;
9
+ reason: string;
10
+ activate_command: string;
11
+ }
12
+ /**
13
+ * Check if a suggestion should be made based on tool result text.
14
+ * Returns a suggestion or null.
15
+ */
16
+ export declare function checkSuggestion(resultText: string, activePacks: string[], proactiveSuggestionsEnabled: boolean): Suggestion | null;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Proactive Skill Pack Suggestions — keyword heuristics on tool results.
3
+ *
4
+ * After each tool call, scans the result text for keywords that map to
5
+ * inactive packs. Suggests activation with cooldown to avoid noise.
6
+ */
7
+ const HEURISTIC_RULES = [
8
+ {
9
+ keywords: ["social", "reddit", "twitter", "tiktok", "youtube", "instagram", "platform"],
10
+ pack: "social-listening",
11
+ reason: "Your work mentions social platforms. Activate social-listening to search across 7 platforms.",
12
+ },
13
+ {
14
+ keywords: ["analysis", "sentiment", "insight", "opinion", "audience", "cluster"],
15
+ pack: "audience-analysis",
16
+ reason: "Your work involves analysis. Activate audience-analysis for AI-powered content analysis and opinion clustering.",
17
+ },
18
+ {
19
+ keywords: ["knowledge", "learning", "context", "decision", "pattern"],
20
+ pack: "knowledge",
21
+ reason: "Your work involves knowledge management. Activate the knowledge pack to query decisions and record learnings.",
22
+ },
23
+ {
24
+ keywords: ["plan", "task", "milestone", "roadmap", "backlog"],
25
+ pack: "planning",
26
+ reason: "Your work involves planning. Activate the planning pack for task management and execution tracking.",
27
+ },
28
+ {
29
+ keywords: ["spec", "requirement", "feature", "implementation", "acceptance"],
30
+ pack: "spec-delivery",
31
+ reason: "Your work involves specifications. Activate spec-delivery to browse and implement specs from analysis.",
32
+ },
33
+ {
34
+ keywords: ["session", "agent", "parallel", "coordinate", "delegate"],
35
+ pack: "sessions",
36
+ reason: "Your work involves multi-agent coordination. Activate sessions to coordinate parallel agent sessions.",
37
+ },
38
+ ];
39
+ // ─── Cooldown ───────────────────────────────────────────────────────────────
40
+ const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
41
+ const lastSuggested = new Map();
42
+ // ─── Public API ─────────────────────────────────────────────────────────────
43
+ /**
44
+ * Check if a suggestion should be made based on tool result text.
45
+ * Returns a suggestion or null.
46
+ */
47
+ export function checkSuggestion(resultText, activePacks, proactiveSuggestionsEnabled) {
48
+ if (!proactiveSuggestionsEnabled)
49
+ return null;
50
+ const lower = resultText.toLowerCase();
51
+ const now = Date.now();
52
+ for (const rule of HEURISTIC_RULES) {
53
+ // Skip if pack already active
54
+ if (activePacks.includes(rule.pack))
55
+ continue;
56
+ // Skip if on cooldown
57
+ const lastTime = lastSuggested.get(rule.pack);
58
+ if (lastTime && now - lastTime < COOLDOWN_MS)
59
+ continue;
60
+ // Check if any keyword matches
61
+ const matched = rule.keywords.some((kw) => lower.includes(kw));
62
+ if (matched) {
63
+ lastSuggested.set(rule.pack, now);
64
+ return {
65
+ pack_id: rule.pack,
66
+ reason: rule.reason,
67
+ activate_command: `activate_skill_pack({ pack_id: '${rule.pack}' })`,
68
+ };
69
+ }
70
+ }
71
+ return null;
72
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Telemetry — Opt-in usage tracking with three privacy tiers.
3
+ *
4
+ * Storage: ~/.crowdlisten/telemetry.jsonl (append-only, one JSON line per event)
5
+ *
6
+ * Tiers:
7
+ * - "off": no recording
8
+ * - "anonymous": local-only, no installationId
9
+ * - "community": includes installationId for aggregate insights
10
+ */
11
+ import type { UserState, TelemetryLevel } from "./context/user-state.js";
12
+ export interface TelemetryEvent {
13
+ ts: string;
14
+ event: "tool_call";
15
+ tool: string;
16
+ pack: string;
17
+ duration_ms: number;
18
+ success: boolean;
19
+ installationId?: string;
20
+ }
21
+ /**
22
+ * Record a telemetry event. Respects the user's telemetry level.
23
+ */
24
+ export declare function recordEvent(event: Omit<TelemetryEvent, "ts" | "installationId">, level: TelemetryLevel, installationId?: string): void;
25
+ /**
26
+ * Check if there's a pending onboarding step.
27
+ * Returns the step name or null if all complete.
28
+ */
29
+ export declare function shouldOnboard(state: UserState): string | null;
30
+ /**
31
+ * Get onboarding prompt content for a step.
32
+ */
33
+ export declare function getOnboardingPrompt(step: string): {
34
+ title: string;
35
+ message: string;
36
+ } | null;
37
+ /**
38
+ * Build the tool-to-pack mapping from the registry at init time.
39
+ * Used by both telemetry (to tag events) and suggestions.
40
+ */
41
+ export declare function buildToolPackMap(packs: Array<{
42
+ id: string;
43
+ toolNames: string[];
44
+ }>): Map<string, string>;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Telemetry — Opt-in usage tracking with three privacy tiers.
3
+ *
4
+ * Storage: ~/.crowdlisten/telemetry.jsonl (append-only, one JSON line per event)
5
+ *
6
+ * Tiers:
7
+ * - "off": no recording
8
+ * - "anonymous": local-only, no installationId
9
+ * - "community": includes installationId for aggregate insights
10
+ */
11
+ import * as fs from "fs";
12
+ import * as path from "path";
13
+ import * as os from "os";
14
+ const BASE_DIR = path.join(os.homedir(), ".crowdlisten");
15
+ const TELEMETRY_FILE = path.join(BASE_DIR, "telemetry.jsonl");
16
+ // ─── Onboarding Steps ───────────────────────────────────────────────────────
17
+ const ONBOARDING_STEPS = ["telemetry", "proactive", "learnings"];
18
+ const ONBOARDING_PROMPTS = {
19
+ telemetry: {
20
+ title: "Usage Telemetry",
21
+ message: "CrowdListen can track tool usage to improve suggestions over time. " +
22
+ "Choose a privacy level:\n" +
23
+ "- **off**: No tracking (default)\n" +
24
+ "- **anonymous**: Local-only usage stats, never shared\n" +
25
+ "- **community**: Anonymous aggregate stats help improve CrowdListen for everyone\n\n" +
26
+ "Set with: set_preferences({ telemetry: 'off' | 'anonymous' | 'community' })",
27
+ },
28
+ proactive: {
29
+ title: "Proactive Skill Suggestions",
30
+ message: "CrowdListen can suggest relevant skill packs based on your usage patterns. " +
31
+ "This is enabled by default. Disable with: set_preferences({ proactive_suggestions: false })",
32
+ },
33
+ learnings: {
34
+ title: "Cross-Project Learnings",
35
+ message: "CrowdListen can persist insights and patterns across projects, so learnings from one project " +
36
+ "help in future work. Disabled by default.\n\n" +
37
+ "Enable with: set_preferences({ cross_project_learnings: true })",
38
+ },
39
+ };
40
+ // ─── Functions ──────────────────────────────────────────────────────────────
41
+ function ensureDir() {
42
+ if (!fs.existsSync(BASE_DIR)) {
43
+ fs.mkdirSync(BASE_DIR, { recursive: true });
44
+ }
45
+ }
46
+ /**
47
+ * Record a telemetry event. Respects the user's telemetry level.
48
+ */
49
+ export function recordEvent(event, level, installationId) {
50
+ if (level === "off")
51
+ return;
52
+ ensureDir();
53
+ const record = {
54
+ ...event,
55
+ ts: new Date().toISOString(),
56
+ };
57
+ // Only include installationId at community tier
58
+ if (level === "community" && installationId) {
59
+ record.installationId = installationId;
60
+ }
61
+ fs.appendFileSync(TELEMETRY_FILE, JSON.stringify(record) + "\n");
62
+ }
63
+ /**
64
+ * Check if there's a pending onboarding step.
65
+ * Returns the step name or null if all complete.
66
+ */
67
+ export function shouldOnboard(state) {
68
+ for (const step of ONBOARDING_STEPS) {
69
+ if (!state.onboardingCompleted.includes(step)) {
70
+ return step;
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ /**
76
+ * Get onboarding prompt content for a step.
77
+ */
78
+ export function getOnboardingPrompt(step) {
79
+ return ONBOARDING_PROMPTS[step] || null;
80
+ }
81
+ /**
82
+ * Build the tool-to-pack mapping from the registry at init time.
83
+ * Used by both telemetry (to tag events) and suggestions.
84
+ */
85
+ export function buildToolPackMap(packs) {
86
+ const map = new Map();
87
+ for (const pack of packs) {
88
+ for (const tool of pack.toolNames) {
89
+ map.set(tool, pack.id);
90
+ }
91
+ }
92
+ return map;
93
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Skill Pack Registry — Progressive Disclosure
3
+ *
4
+ * Groups tools into logical skill packs following the GitHub MCP Server pattern.
5
+ * Start with ~4 discovery tools, activate packs on demand, fire tools/list_changed.
6
+ *
7
+ * Pattern: 85-98% token savings vs static tool lists.
8
+ */
9
+ export interface SkillPack {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ toolNames: string[];
14
+ isVirtual?: boolean;
15
+ skillMdPath?: string;
16
+ }
17
+ export interface SkillPackInfo {
18
+ id: string;
19
+ name: string;
20
+ description: string;
21
+ toolCount: number;
22
+ status: "active" | "available";
23
+ isVirtual: boolean;
24
+ }
25
+ /**
26
+ * Register a tool definition. Called during server init.
27
+ */
28
+ export declare function registerTool(tool: any): void;
29
+ /**
30
+ * Register an array of tool definitions.
31
+ */
32
+ export declare function registerTools(tools: any[]): void;
33
+ /**
34
+ * Built-in skill packs grouping tools into logical units.
35
+ */
36
+ export declare function initializeRegistry(skillsDir: string): void;
37
+ /**
38
+ * Get tool definitions for a set of active pack IDs.
39
+ * Core pack tools are always included.
40
+ */
41
+ export declare function getToolsForPacks(activePackIds: string[]): any[];
42
+ /**
43
+ * Get tool definitions for a single pack.
44
+ */
45
+ export declare function getPackTools(packId: string): any[];
46
+ /**
47
+ * List all packs with their metadata and active status.
48
+ */
49
+ export declare function listPacks(activePackIds: string[]): SkillPackInfo[];
50
+ /**
51
+ * Check if a pack exists.
52
+ */
53
+ export declare function hasPack(packId: string): boolean;
54
+ /**
55
+ * Get a pack by ID.
56
+ */
57
+ export declare function getPack(packId: string): SkillPack | undefined;
58
+ /**
59
+ * Get the SKILL.md content for a virtual pack.
60
+ */
61
+ export declare function getSkillMdContent(packId: string): string | null;
62
+ /**
63
+ * Check if a tool name belongs to the insights module.
64
+ */
65
+ export declare function isInsightsTool(name: string): boolean;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Skill Pack Registry — Progressive Disclosure
3
+ *
4
+ * Groups tools into logical skill packs following the GitHub MCP Server pattern.
5
+ * Start with ~4 discovery tools, activate packs on demand, fire tools/list_changed.
6
+ *
7
+ * Pattern: 85-98% token savings vs static tool lists.
8
+ */
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ import { INSIGHTS_TOOLS } from "../insights/index.js";
12
+ // ─── Tool Definitions (imported from other modules) ─────────────────────────
13
+ // We store tools in a flat map for lookup
14
+ const toolMap = new Map();
15
+ /**
16
+ * Register a tool definition. Called during server init.
17
+ */
18
+ export function registerTool(tool) {
19
+ toolMap.set(tool.name, tool);
20
+ }
21
+ /**
22
+ * Register an array of tool definitions.
23
+ */
24
+ export function registerTools(tools) {
25
+ for (const tool of tools) {
26
+ toolMap.set(tool.name, tool);
27
+ }
28
+ }
29
+ // ─── Pack Definitions ──────────────────────────────────────────────────────
30
+ const packs = new Map();
31
+ /**
32
+ * Built-in skill packs grouping tools into logical units.
33
+ */
34
+ export function initializeRegistry(skillsDir) {
35
+ // Core — always on (5 tools)
36
+ packs.set("core", {
37
+ id: "core",
38
+ name: "Core",
39
+ description: "Discovery + memory — list skill packs, activate packs, save and recall context with semantic search",
40
+ toolNames: ["list_skill_packs", "activate_skill_pack", "save", "recall", "set_preferences"],
41
+ });
42
+ // Planning — task management
43
+ packs.set("planning", {
44
+ id: "planning",
45
+ name: "Planning & Tasks",
46
+ description: "Task management, planning, and execution tracking — create tasks, build plans, track progress",
47
+ toolNames: [
48
+ "list_tasks", "create_task", "get_task", "update_task",
49
+ "claim_task", "complete_task", "log_progress", "delete_task",
50
+ "create_plan", "get_plan", "update_plan",
51
+ ],
52
+ });
53
+ // Knowledge pack removed — consolidated into core save/recall
54
+ // Social listening — free tools
55
+ packs.set("social-listening", {
56
+ id: "social-listening",
57
+ name: "Social Listening",
58
+ description: "Search and retrieve content from social platforms — Reddit, Twitter, TikTok, YouTube, Instagram. Free, no API key needed.",
59
+ toolNames: [
60
+ "search_content", "get_content_comments", "get_trending_content",
61
+ "get_user_content", "get_platform_status", "health_check", "extract_url",
62
+ ],
63
+ });
64
+ // Audience analysis — paid tools
65
+ packs.set("audience-analysis", {
66
+ id: "audience-analysis",
67
+ name: "Audience Analysis",
68
+ description: "AI-powered content analysis, opinion clustering, insight extraction, and cross-platform research synthesis. Requires CROWDLISTEN_API_KEY.",
69
+ toolNames: [
70
+ "analyze_content", "cluster_opinions", "enrich_content",
71
+ "deep_analyze", "extract_insights", "research_synthesis",
72
+ ],
73
+ });
74
+ // Sessions — multi-agent coordination
75
+ packs.set("sessions", {
76
+ id: "sessions",
77
+ name: "Multi-Agent Sessions",
78
+ description: "Coordinate parallel agent sessions on shared tasks",
79
+ toolNames: ["start_session", "list_sessions", "update_session"],
80
+ });
81
+ // Setup — board management
82
+ packs.set("setup", {
83
+ id: "setup",
84
+ name: "Setup & Board Management",
85
+ description: "Project and board setup — list projects, create boards, migrate tasks",
86
+ toolNames: [
87
+ "get_or_create_global_board", "list_projects",
88
+ "list_boards", "create_board", "migrate_to_global_board",
89
+ ],
90
+ });
91
+ // Spec Delivery — actionable specs from crowd analysis
92
+ packs.set("spec-delivery", {
93
+ id: "spec-delivery",
94
+ name: "Spec Delivery",
95
+ description: "Actionable specs from crowd feedback — browse, inspect, and start implementing specs generated from analysis",
96
+ toolNames: ["get_specs", "get_spec_detail", "start_spec"],
97
+ });
98
+ // ── Agent-Proxied Packs (proxy to agent.crowdlisten.com) ────────────
99
+ // Analysis — run full audience analysis, continue, list results
100
+ packs.set("analysis", {
101
+ id: "analysis",
102
+ name: "Analysis Engine",
103
+ description: "Run audience analyses, continue with follow-ups, list results, generate specs. Requires CROWDLISTEN_API_KEY.",
104
+ toolNames: [
105
+ "run_analysis", "continue_analysis", "get_analysis",
106
+ "list_analyses", "generate_specs",
107
+ ],
108
+ });
109
+ // Content & Vectors — ingest, search, manage content
110
+ packs.set("content", {
111
+ id: "content",
112
+ name: "Content & Vectors",
113
+ description: "Ingest content for vector storage, semantic search, stats, deletion. Requires CROWDLISTEN_API_KEY.",
114
+ toolNames: ["ingest_content", "search_vectors", "get_content_stats", "delete_content"],
115
+ });
116
+ // Document Generation — PRDs from analysis
117
+ packs.set("generation", {
118
+ id: "generation",
119
+ name: "Document Generation",
120
+ description: "Generate PRDs from analysis results, update sections. Requires CROWDLISTEN_API_KEY.",
121
+ toolNames: ["generate_prd", "update_prd_section"],
122
+ });
123
+ // LLM Proxy — free completion and model listing
124
+ packs.set("llm", {
125
+ id: "llm",
126
+ name: "LLM Proxy",
127
+ description: "Free LLM completion proxy — no API key needed. List available models and run completions.",
128
+ toolNames: ["llm_complete", "list_llm_models"],
129
+ });
130
+ // Agent Network — register, discover, share
131
+ packs.set("agent-network", {
132
+ id: "agent-network",
133
+ name: "Agent Network",
134
+ description: "Register agents, discover capabilities, submit analysis results to the network.",
135
+ toolNames: ["register_agent", "get_capabilities", "submit_analysis"],
136
+ });
137
+ // Legacy — context extraction tools (hidden by default for existing users, replaced by remember/recall)
138
+ packs.set("legacy", {
139
+ id: "legacy",
140
+ name: "Legacy Context Extraction",
141
+ description: "Previous-gen context tools — process transcripts, discover skills, install skills. Replaced by remember/recall and list_skill_packs.",
142
+ toolNames: [
143
+ "process_transcript", "get_context_blocks", "recommend_skills",
144
+ "discover_skills", "search_skills", "install_skill",
145
+ ],
146
+ });
147
+ // ── Virtual SKILL.md Packs ────────────────────────────────────────────
148
+ // Each native skill directory becomes a virtual pack
149
+ if (fs.existsSync(skillsDir)) {
150
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ if (!entry.isDirectory())
153
+ continue;
154
+ const skillMd = path.join(skillsDir, entry.name, "SKILL.md");
155
+ if (!fs.existsSync(skillMd))
156
+ continue;
157
+ packs.set(entry.name, {
158
+ id: entry.name,
159
+ name: entry.name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
160
+ description: `Skill workflow: ${entry.name}. Activate to load SKILL.md instructions.`,
161
+ toolNames: [],
162
+ isVirtual: true,
163
+ skillMdPath: skillMd,
164
+ });
165
+ }
166
+ }
167
+ }
168
+ // ─── Public API ─────────────────────────────────────────────────────────────
169
+ /**
170
+ * Get tool definitions for a set of active pack IDs.
171
+ * Core pack tools are always included.
172
+ */
173
+ export function getToolsForPacks(activePackIds) {
174
+ const toolNames = new Set();
175
+ // Core is always included
176
+ const corePack = packs.get("core");
177
+ if (corePack) {
178
+ for (const name of corePack.toolNames)
179
+ toolNames.add(name);
180
+ }
181
+ for (const packId of activePackIds) {
182
+ const pack = packs.get(packId);
183
+ if (!pack || pack.isVirtual)
184
+ continue;
185
+ for (const name of pack.toolNames)
186
+ toolNames.add(name);
187
+ }
188
+ const tools = [];
189
+ for (const name of toolNames) {
190
+ const tool = toolMap.get(name);
191
+ if (tool)
192
+ tools.push(tool);
193
+ }
194
+ return tools;
195
+ }
196
+ /**
197
+ * Get tool definitions for a single pack.
198
+ */
199
+ export function getPackTools(packId) {
200
+ const pack = packs.get(packId);
201
+ if (!pack)
202
+ return [];
203
+ return pack.toolNames
204
+ .map(name => toolMap.get(name))
205
+ .filter(Boolean);
206
+ }
207
+ /**
208
+ * List all packs with their metadata and active status.
209
+ */
210
+ export function listPacks(activePackIds) {
211
+ const result = [];
212
+ for (const pack of packs.values()) {
213
+ result.push({
214
+ id: pack.id,
215
+ name: pack.name,
216
+ description: pack.description,
217
+ toolCount: pack.toolNames.length,
218
+ status: activePackIds.includes(pack.id) || pack.id === "core" ? "active" : "available",
219
+ isVirtual: pack.isVirtual || false,
220
+ });
221
+ }
222
+ return result;
223
+ }
224
+ /**
225
+ * Check if a pack exists.
226
+ */
227
+ export function hasPack(packId) {
228
+ return packs.has(packId);
229
+ }
230
+ /**
231
+ * Get a pack by ID.
232
+ */
233
+ export function getPack(packId) {
234
+ return packs.get(packId);
235
+ }
236
+ /**
237
+ * Get the SKILL.md content for a virtual pack.
238
+ */
239
+ export function getSkillMdContent(packId) {
240
+ const pack = packs.get(packId);
241
+ if (!pack?.isVirtual || !pack.skillMdPath)
242
+ return null;
243
+ try {
244
+ return fs.readFileSync(pack.skillMdPath, "utf-8");
245
+ }
246
+ catch {
247
+ return null;
248
+ }
249
+ }
250
+ /**
251
+ * Check if a tool name belongs to the insights module.
252
+ */
253
+ export function isInsightsTool(name) {
254
+ const insightsToolNames = new Set(INSIGHTS_TOOLS.map((t) => t.name));
255
+ return insightsToolNames.has(name);
256
+ }