@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.
- package/AGENTS.md +167 -0
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/agent-proxy.d.ts +24 -0
- package/dist/agent-proxy.js +140 -0
- package/dist/agent-tools.d.ts +736 -0
- package/dist/agent-tools.js +409 -0
- package/dist/context/api.d.ts +5 -0
- package/dist/context/api.js +164 -0
- package/dist/context/cli.d.ts +19 -0
- package/dist/context/cli.js +108 -0
- package/dist/context/extractor.d.ts +12 -0
- package/dist/context/extractor.js +43 -0
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.js +11 -0
- package/dist/context/matcher.d.ts +39 -0
- package/dist/context/matcher.js +246 -0
- package/dist/context/parser.d.ts +28 -0
- package/dist/context/parser.js +157 -0
- package/dist/context/pipeline.d.ts +26 -0
- package/dist/context/pipeline.js +56 -0
- package/dist/context/prompts.d.ts +6 -0
- package/dist/context/prompts.js +60 -0
- package/dist/context/providers.d.ts +6 -0
- package/dist/context/providers.js +106 -0
- package/dist/context/redactor.d.ts +10 -0
- package/dist/context/redactor.js +68 -0
- package/dist/context/server.d.ts +5 -0
- package/dist/context/server.js +134 -0
- package/dist/context/store.d.ts +12 -0
- package/dist/context/store.js +82 -0
- package/dist/context/types.d.ts +79 -0
- package/dist/context/types.js +4 -0
- package/dist/context/user-state.d.ts +40 -0
- package/dist/context/user-state.js +144 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +385 -0
- package/dist/insights/browser/BrowserPool.d.ts +87 -0
- package/dist/insights/browser/BrowserPool.js +266 -0
- package/dist/insights/browser/RequestInterceptor.d.ts +46 -0
- package/dist/insights/browser/RequestInterceptor.js +115 -0
- package/dist/insights/cli.d.ts +8 -0
- package/dist/insights/cli.js +206 -0
- package/dist/insights/core/base/BaseAdapter.d.ts +37 -0
- package/dist/insights/core/base/BaseAdapter.js +123 -0
- package/dist/insights/core/health/HealthMonitor.d.ts +75 -0
- package/dist/insights/core/health/HealthMonitor.js +171 -0
- package/dist/insights/core/interfaces/SocialMediaPlatform.d.ts +125 -0
- package/dist/insights/core/interfaces/SocialMediaPlatform.js +42 -0
- package/dist/insights/core/utils/DataNormalizer.d.ts +53 -0
- package/dist/insights/core/utils/DataNormalizer.js +349 -0
- package/dist/insights/core/utils/InstagramUrlUtils.d.ts +11 -0
- package/dist/insights/core/utils/InstagramUrlUtils.js +60 -0
- package/dist/insights/core/utils/TikTokUrlUtils.d.ts +10 -0
- package/dist/insights/core/utils/TikTokUrlUtils.js +57 -0
- package/dist/insights/handlers.d.ts +157 -0
- package/dist/insights/handlers.js +246 -0
- package/dist/insights/index.d.ts +437 -0
- package/dist/insights/index.js +426 -0
- package/dist/insights/platforms/instagram/InstagramAdapter.d.ts +34 -0
- package/dist/insights/platforms/instagram/InstagramAdapter.js +342 -0
- package/dist/insights/platforms/moltbook/MoltbookAdapter.d.ts +31 -0
- package/dist/insights/platforms/moltbook/MoltbookAdapter.js +227 -0
- package/dist/insights/platforms/reddit/RedditAdapter.d.ts +21 -0
- package/dist/insights/platforms/reddit/RedditAdapter.js +212 -0
- package/dist/insights/platforms/tiktok/TikTokAdapter.d.ts +34 -0
- package/dist/insights/platforms/tiktok/TikTokAdapter.js +269 -0
- package/dist/insights/platforms/twitter/TwitterAdapter.d.ts +23 -0
- package/dist/insights/platforms/twitter/TwitterAdapter.js +211 -0
- package/dist/insights/platforms/xiaohongshu/XiaohongshuAdapter.d.ts +35 -0
- package/dist/insights/platforms/xiaohongshu/XiaohongshuAdapter.js +258 -0
- package/dist/insights/platforms/youtube/YouTubeAdapter.d.ts +22 -0
- package/dist/insights/platforms/youtube/YouTubeAdapter.js +254 -0
- package/dist/insights/service-config.d.ts +7 -0
- package/dist/insights/service-config.js +60 -0
- package/dist/insights/services/UnifiedSocialMediaService.d.ts +94 -0
- package/dist/insights/services/UnifiedSocialMediaService.js +259 -0
- package/dist/insights/vision/VisionExtractor.d.ts +46 -0
- package/dist/insights/vision/VisionExtractor.js +236 -0
- package/dist/learnings.d.ts +50 -0
- package/dist/learnings.js +130 -0
- package/dist/openapi.d.ts +29 -0
- package/dist/openapi.js +169 -0
- package/dist/server-factory.d.ts +20 -0
- package/dist/server-factory.js +41 -0
- package/dist/suggestions.d.ts +16 -0
- package/dist/suggestions.js +72 -0
- package/dist/telemetry.d.ts +44 -0
- package/dist/telemetry.js +93 -0
- package/dist/tools/registry.d.ts +65 -0
- package/dist/tools/registry.js +256 -0
- package/dist/tools.d.ts +2433 -0
- package/dist/tools.js +2294 -0
- package/dist/transport/http.d.ts +15 -0
- package/dist/transport/http.js +154 -0
- package/package.json +76 -0
- package/skills/catalog.json +272 -0
- package/skills/community-catalog.json +4202 -0
- package/skills/competitive-analysis/SKILL.md +174 -0
- package/skills/content-creator/SKILL.md +256 -0
- package/skills/content-strategy/SKILL.md +222 -0
- package/skills/data-storytelling/SKILL.md +248 -0
- package/skills/heuristic-evaluation/SKILL.md +201 -0
- package/skills/market-research-reports/SKILL.md +184 -0
- package/skills/user-stories/SKILL.md +178 -0
- package/skills/ux-researcher/SKILL.md +239 -0
- package/web-dist/assets/index-B1b25lNd.css +1 -0
- package/web-dist/assets/index-CDWHwHbl.js +64 -0
- package/web-dist/index.html +16 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-Proxied Tools — Tools that proxy to agent.crowdlisten.com
|
|
3
|
+
*
|
|
4
|
+
* 5 skill packs, 16 tools. Each tool calls the agent backend via
|
|
5
|
+
* the shared agent-proxy helpers.
|
|
6
|
+
*
|
|
7
|
+
* Free vs Paid:
|
|
8
|
+
* - Free: llm (2 tools), agent-network (2 tools) — no API key needed
|
|
9
|
+
* - Paid: analysis (5), content (4), generation (2) — require CROWDLISTEN_API_KEY
|
|
10
|
+
*/
|
|
11
|
+
import { agentPost, agentGet, agentDelete, agentStream, requireApiKey, } from "./agent-proxy.js";
|
|
12
|
+
// ─── Tool Definitions ──────────────────────────────────────────────────────
|
|
13
|
+
export const AGENT_TOOLS = [
|
|
14
|
+
// ── Analysis Pack (5 tools) ────────────────────────────────────────────
|
|
15
|
+
{
|
|
16
|
+
name: "run_analysis",
|
|
17
|
+
description: "[Analysis] Run an audience analysis on a project. Streams results via SSE — returns the final analysis with themes, sentiment, insights, and recommendations. Requires CROWDLISTEN_API_KEY.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
22
|
+
question: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Research question to analyze (e.g., 'What do users think about our pricing?')",
|
|
25
|
+
},
|
|
26
|
+
platforms: {
|
|
27
|
+
type: "array",
|
|
28
|
+
items: { type: "string" },
|
|
29
|
+
description: "Platforms to search: reddit, youtube, tiktok, twitter, instagram, xiaohongshu. Default: all.",
|
|
30
|
+
},
|
|
31
|
+
max_results: {
|
|
32
|
+
type: "number",
|
|
33
|
+
description: "Max results per platform (default 20)",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ["project_id", "question"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "continue_analysis",
|
|
41
|
+
description: "[Analysis] Continue a previous analysis with a follow-up question. Builds on existing context. Requires CROWDLISTEN_API_KEY.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
analysis_id: { type: "string", description: "Analysis UUID from run_analysis" },
|
|
46
|
+
question: { type: "string", description: "Follow-up question" },
|
|
47
|
+
},
|
|
48
|
+
required: ["analysis_id", "question"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "get_analysis",
|
|
53
|
+
description: "[Analysis] Get the full results of a completed analysis including themes, sentiment breakdown, and source quotes. Requires CROWDLISTEN_API_KEY.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
analysis_id: { type: "string", description: "Analysis UUID" },
|
|
58
|
+
},
|
|
59
|
+
required: ["analysis_id"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "list_analyses",
|
|
64
|
+
description: "[Analysis] List analyses for a project, newest first. Requires CROWDLISTEN_API_KEY.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
69
|
+
limit: { type: "number", description: "Max results (default 20)" },
|
|
70
|
+
},
|
|
71
|
+
required: ["project_id"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "generate_specs",
|
|
76
|
+
description: "[Analysis] Generate product specifications from analysis results — feature requests, user stories, and acceptance criteria extracted from audience signal. Requires CROWDLISTEN_API_KEY.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
81
|
+
analysis_id: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Optional: scope to a specific analysis",
|
|
84
|
+
},
|
|
85
|
+
spec_type: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "Type: feature_requests, user_stories, acceptance_criteria, all. Default: all.",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ["project_id"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
// ── Content Pack (4 tools) ─────────────────────────────────────────────
|
|
94
|
+
{
|
|
95
|
+
name: "ingest_content",
|
|
96
|
+
description: "[Content] Ingest content into the vector store for semantic search. Accepts raw text, URLs, or structured content. Requires CROWDLISTEN_API_KEY.",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
101
|
+
content: { type: "string", description: "Text content to ingest" },
|
|
102
|
+
source_url: { type: "string", description: "Optional: source URL" },
|
|
103
|
+
title: { type: "string", description: "Optional: content title" },
|
|
104
|
+
metadata: {
|
|
105
|
+
type: "object",
|
|
106
|
+
description: "Optional: key-value metadata",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ["project_id", "content"],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "search_vectors",
|
|
114
|
+
description: "[Content] Semantic search across ingested content using vector embeddings. Returns ranked results with relevance scores. Requires CROWDLISTEN_API_KEY.",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
119
|
+
query: { type: "string", description: "Search query" },
|
|
120
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
121
|
+
threshold: {
|
|
122
|
+
type: "number",
|
|
123
|
+
description: "Minimum similarity score 0-1 (default 0.5)",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ["project_id", "query"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "get_content_stats",
|
|
131
|
+
description: "[Content] Get statistics about ingested content — document count, total chunks, storage usage. Requires CROWDLISTEN_API_KEY.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
136
|
+
},
|
|
137
|
+
required: ["project_id"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "delete_content",
|
|
142
|
+
description: "[Content] Delete a specific content document and its vector embeddings. Requires CROWDLISTEN_API_KEY.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
content_id: { type: "string", description: "Content document UUID" },
|
|
147
|
+
},
|
|
148
|
+
required: ["content_id"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
// ── Generation Pack (2 tools) ──────────────────────────────────────────
|
|
152
|
+
{
|
|
153
|
+
name: "generate_prd",
|
|
154
|
+
description: "[Generation] Generate a Product Requirements Document from analysis results. Streams the PRD as it's generated. Requires CROWDLISTEN_API_KEY.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
project_id: { type: "string", description: "Project UUID" },
|
|
159
|
+
analysis_ids: {
|
|
160
|
+
type: "array",
|
|
161
|
+
items: { type: "string" },
|
|
162
|
+
description: "Analysis UUIDs to base the PRD on",
|
|
163
|
+
},
|
|
164
|
+
template: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "PRD template: standard, lean, technical, marketing. Default: standard.",
|
|
167
|
+
},
|
|
168
|
+
sections: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: { type: "string" },
|
|
171
|
+
description: "Specific sections to generate (default: all). Options: overview, problem, solution, requirements, metrics, timeline",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
required: ["project_id"],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "update_prd_section",
|
|
179
|
+
description: "[Generation] Update a specific section of an existing PRD with new content or instructions. Requires CROWDLISTEN_API_KEY.",
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
document_id: { type: "string", description: "PRD document UUID" },
|
|
184
|
+
section: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Section to update: overview, problem, solution, requirements, metrics, timeline",
|
|
187
|
+
},
|
|
188
|
+
instructions: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Instructions for how to update the section",
|
|
191
|
+
},
|
|
192
|
+
content: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "Optional: replacement content (overrides instructions-based generation)",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
required: ["document_id", "section"],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
// ── LLM Pack (2 tools) — FREE ─────────────────────────────────────────
|
|
201
|
+
{
|
|
202
|
+
name: "llm_complete",
|
|
203
|
+
description: "[LLM] Send a completion request through CrowdListen's LLM proxy. Free tier — no API key required. Supports multiple models.",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
prompt: { type: "string", description: "The prompt to complete" },
|
|
208
|
+
model: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "Model to use (default: gpt-4o-mini). Call list_llm_models for available options.",
|
|
211
|
+
},
|
|
212
|
+
max_tokens: {
|
|
213
|
+
type: "number",
|
|
214
|
+
description: "Max tokens in response (default 1000)",
|
|
215
|
+
},
|
|
216
|
+
temperature: {
|
|
217
|
+
type: "number",
|
|
218
|
+
description: "Temperature 0-2 (default 0.7)",
|
|
219
|
+
},
|
|
220
|
+
system: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Optional system prompt",
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
required: ["prompt"],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "list_llm_models",
|
|
230
|
+
description: "[LLM] List available LLM models and their capabilities. Free — no API key required.",
|
|
231
|
+
inputSchema: { type: "object", properties: {} },
|
|
232
|
+
},
|
|
233
|
+
// ── Agent Network Pack (3 tools) — Mixed auth ─────────────────────────
|
|
234
|
+
{
|
|
235
|
+
name: "register_agent",
|
|
236
|
+
description: "[Agent Network] Register this agent in the CrowdListen agent network. Free — no API key required. Returns agent_id for future interactions.",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
name: { type: "string", description: "Agent display name" },
|
|
241
|
+
capabilities: {
|
|
242
|
+
type: "array",
|
|
243
|
+
items: { type: "string" },
|
|
244
|
+
description: "Agent capabilities: analysis, planning, coding, research, content",
|
|
245
|
+
},
|
|
246
|
+
executor: {
|
|
247
|
+
type: "string",
|
|
248
|
+
description: "Agent runtime: CLAUDE_CODE, CURSOR, GEMINI, CODEX, AMP, OPENCLAW",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
required: ["name"],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "get_capabilities",
|
|
256
|
+
description: "[Agent Network] List capabilities of agents in the network. Free — no API key required.",
|
|
257
|
+
inputSchema: { type: "object", properties: {} },
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "submit_analysis",
|
|
261
|
+
description: "[Agent Network] Submit an analysis result from this agent to the network for cross-agent synthesis. Requires CROWDLISTEN_API_KEY.",
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: "object",
|
|
264
|
+
properties: {
|
|
265
|
+
agent_id: { type: "string", description: "Your agent_id from register_agent" },
|
|
266
|
+
analysis_id: { type: "string", description: "Analysis UUID to share" },
|
|
267
|
+
summary: { type: "string", description: "Brief summary of findings" },
|
|
268
|
+
},
|
|
269
|
+
required: ["agent_id", "analysis_id", "summary"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
// ─── Agent Tool Names (for dispatch routing) ───────────────────────────────
|
|
274
|
+
const AGENT_TOOL_NAMES = new Set(AGENT_TOOLS.map((t) => t.name));
|
|
275
|
+
export function isAgentTool(name) {
|
|
276
|
+
return AGENT_TOOL_NAMES.has(name);
|
|
277
|
+
}
|
|
278
|
+
// ─── Free tools (no API key required) ──────────────────────────────────────
|
|
279
|
+
const FREE_TOOLS = new Set([
|
|
280
|
+
"llm_complete",
|
|
281
|
+
"list_llm_models",
|
|
282
|
+
"register_agent",
|
|
283
|
+
"get_capabilities",
|
|
284
|
+
]);
|
|
285
|
+
// ─── Handler ───────────────────────────────────────────────────────────────
|
|
286
|
+
export async function handleAgentTool(name, args) {
|
|
287
|
+
const needsKey = !FREE_TOOLS.has(name);
|
|
288
|
+
const apiKey = needsKey ? requireApiKey() : process.env.CROWDLISTEN_API_KEY;
|
|
289
|
+
switch (name) {
|
|
290
|
+
// ── Analysis ───────────────────────────────────────────────
|
|
291
|
+
case "run_analysis": {
|
|
292
|
+
const result = await agentStream("/agent/v1/analysis/run", {
|
|
293
|
+
project_id: args.project_id,
|
|
294
|
+
question: args.question,
|
|
295
|
+
platforms: args.platforms,
|
|
296
|
+
max_results: args.max_results,
|
|
297
|
+
}, apiKey, 180_000 // 3 min timeout for analysis
|
|
298
|
+
);
|
|
299
|
+
return JSON.stringify(result, null, 2);
|
|
300
|
+
}
|
|
301
|
+
case "continue_analysis": {
|
|
302
|
+
const result = await agentPost(`/agent/v1/analysis/${args.analysis_id}/continue`, { question: args.question }, apiKey);
|
|
303
|
+
return JSON.stringify(result, null, 2);
|
|
304
|
+
}
|
|
305
|
+
case "get_analysis": {
|
|
306
|
+
const result = await agentGet(`/agent/v1/analysis/${args.analysis_id}`, apiKey);
|
|
307
|
+
return JSON.stringify(result, null, 2);
|
|
308
|
+
}
|
|
309
|
+
case "list_analyses": {
|
|
310
|
+
const limit = args.limit || 20;
|
|
311
|
+
const result = await agentGet(`/agent/v1/projects/${args.project_id}/analyses?limit=${limit}`, apiKey);
|
|
312
|
+
return JSON.stringify(result, null, 2);
|
|
313
|
+
}
|
|
314
|
+
case "generate_specs": {
|
|
315
|
+
const result = await agentPost("/agent/v1/specs/generate", {
|
|
316
|
+
project_id: args.project_id,
|
|
317
|
+
analysis_id: args.analysis_id,
|
|
318
|
+
spec_type: args.spec_type,
|
|
319
|
+
}, apiKey);
|
|
320
|
+
return JSON.stringify(result, null, 2);
|
|
321
|
+
}
|
|
322
|
+
// ── Content ────────────────────────────────────────────────
|
|
323
|
+
case "ingest_content": {
|
|
324
|
+
const result = await agentPost("/agent/v1/content/ingest", {
|
|
325
|
+
project_id: args.project_id,
|
|
326
|
+
content: args.content,
|
|
327
|
+
source_url: args.source_url,
|
|
328
|
+
title: args.title,
|
|
329
|
+
metadata: args.metadata,
|
|
330
|
+
}, apiKey);
|
|
331
|
+
return JSON.stringify(result, null, 2);
|
|
332
|
+
}
|
|
333
|
+
case "search_vectors": {
|
|
334
|
+
const result = await agentPost("/agent/v1/content/search", {
|
|
335
|
+
project_id: args.project_id,
|
|
336
|
+
query: args.query,
|
|
337
|
+
limit: args.limit,
|
|
338
|
+
threshold: args.threshold,
|
|
339
|
+
}, apiKey);
|
|
340
|
+
return JSON.stringify(result, null, 2);
|
|
341
|
+
}
|
|
342
|
+
case "get_content_stats": {
|
|
343
|
+
const result = await agentGet(`/agent/v1/content/stats?project_id=${args.project_id}`, apiKey);
|
|
344
|
+
return JSON.stringify(result, null, 2);
|
|
345
|
+
}
|
|
346
|
+
case "delete_content": {
|
|
347
|
+
const result = await agentDelete(`/agent/v1/content/${args.content_id}`, apiKey);
|
|
348
|
+
return JSON.stringify(result, null, 2);
|
|
349
|
+
}
|
|
350
|
+
// ── Generation ─────────────────────────────────────────────
|
|
351
|
+
case "generate_prd": {
|
|
352
|
+
const result = await agentStream("/agent/v1/prd/generate", {
|
|
353
|
+
project_id: args.project_id,
|
|
354
|
+
analysis_ids: args.analysis_ids,
|
|
355
|
+
template: args.template,
|
|
356
|
+
sections: args.sections,
|
|
357
|
+
}, apiKey, 180_000);
|
|
358
|
+
return JSON.stringify(result, null, 2);
|
|
359
|
+
}
|
|
360
|
+
case "update_prd_section": {
|
|
361
|
+
const result = await agentPost("/agent/v1/prd/update-section", {
|
|
362
|
+
document_id: args.document_id,
|
|
363
|
+
section: args.section,
|
|
364
|
+
instructions: args.instructions,
|
|
365
|
+
content: args.content,
|
|
366
|
+
}, apiKey);
|
|
367
|
+
return JSON.stringify(result, null, 2);
|
|
368
|
+
}
|
|
369
|
+
// ── LLM (Free) ────────────────────────────────────────────
|
|
370
|
+
case "llm_complete": {
|
|
371
|
+
const result = await agentPost("/api/v1/llm/complete", {
|
|
372
|
+
prompt: args.prompt,
|
|
373
|
+
model: args.model,
|
|
374
|
+
max_tokens: args.max_tokens,
|
|
375
|
+
temperature: args.temperature,
|
|
376
|
+
system: args.system,
|
|
377
|
+
}, apiKey // Optional — may be undefined for free tier
|
|
378
|
+
);
|
|
379
|
+
return JSON.stringify(result, null, 2);
|
|
380
|
+
}
|
|
381
|
+
case "list_llm_models": {
|
|
382
|
+
const result = await agentGet("/api/v1/llm/models", apiKey);
|
|
383
|
+
return JSON.stringify(result, null, 2);
|
|
384
|
+
}
|
|
385
|
+
// ── Agent Network ──────────────────────────────────────────
|
|
386
|
+
case "register_agent": {
|
|
387
|
+
const result = await agentPost("/api/agents/register", {
|
|
388
|
+
name: args.name,
|
|
389
|
+
capabilities: args.capabilities,
|
|
390
|
+
executor: args.executor,
|
|
391
|
+
}, apiKey);
|
|
392
|
+
return JSON.stringify(result, null, 2);
|
|
393
|
+
}
|
|
394
|
+
case "get_capabilities": {
|
|
395
|
+
const result = await agentGet("/api/agents/capabilities", apiKey);
|
|
396
|
+
return JSON.stringify(result, null, 2);
|
|
397
|
+
}
|
|
398
|
+
case "submit_analysis": {
|
|
399
|
+
const result = await agentPost("/api/agents/analyze", {
|
|
400
|
+
agent_id: args.agent_id,
|
|
401
|
+
analysis_id: args.analysis_id,
|
|
402
|
+
summary: args.summary,
|
|
403
|
+
}, apiKey);
|
|
404
|
+
return JSON.stringify(result, null, 2);
|
|
405
|
+
}
|
|
406
|
+
default:
|
|
407
|
+
throw new Error(`Unknown agent tool: ${name}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST API routes for the context extraction web UI.
|
|
3
|
+
*/
|
|
4
|
+
import { runPipeline } from "./pipeline.js";
|
|
5
|
+
import { getBlocks, deleteBlock, updateBlock, clearBlocks, loadConfig, saveConfig, getHistory, } from "./store.js";
|
|
6
|
+
import { matchSkills, discoverSkills, searchSkills, getSkillCategories } from "./matcher.js";
|
|
7
|
+
import { loadUserState, activatePack, deactivatePack } from "./user-state.js";
|
|
8
|
+
function readBody(req) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
let data = "";
|
|
11
|
+
req.on("data", (chunk) => (data += chunk.toString()));
|
|
12
|
+
req.on("end", () => resolve(data));
|
|
13
|
+
req.on("error", reject);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function json(res, data, status = 200) {
|
|
17
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
18
|
+
res.end(JSON.stringify(data));
|
|
19
|
+
}
|
|
20
|
+
function error(res, message, status = 400) {
|
|
21
|
+
json(res, { error: message }, status);
|
|
22
|
+
}
|
|
23
|
+
export async function handleApiRequest(req, res, url) {
|
|
24
|
+
const method = req.method || "GET";
|
|
25
|
+
const pathname = url.pathname;
|
|
26
|
+
try {
|
|
27
|
+
// POST /api/process — Process text through the pipeline
|
|
28
|
+
if (pathname === "/api/process" && method === "POST") {
|
|
29
|
+
const body = JSON.parse(await readBody(req));
|
|
30
|
+
const { text, source, isChat } = body;
|
|
31
|
+
if (!text) {
|
|
32
|
+
return error(res, "Missing 'text' field");
|
|
33
|
+
}
|
|
34
|
+
const result = await runPipeline({
|
|
35
|
+
text,
|
|
36
|
+
source: source || "web-upload",
|
|
37
|
+
isChat: isChat !== false,
|
|
38
|
+
});
|
|
39
|
+
return json(res, result);
|
|
40
|
+
}
|
|
41
|
+
// GET /api/blocks — Get stored context blocks
|
|
42
|
+
if (pathname === "/api/blocks" && method === "GET") {
|
|
43
|
+
return json(res, { blocks: getBlocks() });
|
|
44
|
+
}
|
|
45
|
+
// DELETE /api/blocks/:index — Delete a block by index
|
|
46
|
+
if (pathname.startsWith("/api/blocks/") && method === "DELETE") {
|
|
47
|
+
const index = parseInt(pathname.split("/").pop() || "", 10);
|
|
48
|
+
if (isNaN(index))
|
|
49
|
+
return error(res, "Invalid block index");
|
|
50
|
+
deleteBlock(index);
|
|
51
|
+
return json(res, { ok: true });
|
|
52
|
+
}
|
|
53
|
+
// PUT /api/blocks/:index — Update a block by index
|
|
54
|
+
if (pathname.startsWith("/api/blocks/") && method === "PUT") {
|
|
55
|
+
const index = parseInt(pathname.split("/").pop() || "", 10);
|
|
56
|
+
if (isNaN(index))
|
|
57
|
+
return error(res, "Invalid block index");
|
|
58
|
+
const body = JSON.parse(await readBody(req));
|
|
59
|
+
updateBlock(index, body);
|
|
60
|
+
return json(res, { ok: true });
|
|
61
|
+
}
|
|
62
|
+
// DELETE /api/blocks — Clear all blocks
|
|
63
|
+
if (pathname === "/api/blocks" && method === "DELETE") {
|
|
64
|
+
clearBlocks();
|
|
65
|
+
return json(res, { ok: true });
|
|
66
|
+
}
|
|
67
|
+
// GET /api/skills — Get skill recommendations from stored blocks
|
|
68
|
+
if (pathname === "/api/skills" && method === "GET") {
|
|
69
|
+
const blocks = getBlocks();
|
|
70
|
+
const skills = await matchSkills(blocks);
|
|
71
|
+
return json(res, { skills });
|
|
72
|
+
}
|
|
73
|
+
// GET /api/skills/discover — Context-driven skill discovery
|
|
74
|
+
if (pathname === "/api/skills/discover" && method === "GET") {
|
|
75
|
+
const blocks = getBlocks();
|
|
76
|
+
const category = url.searchParams.get("category");
|
|
77
|
+
const tier = url.searchParams.get("tier");
|
|
78
|
+
const limit = parseInt(url.searchParams.get("limit") || "10", 10);
|
|
79
|
+
const skills = await discoverSkills(blocks, {
|
|
80
|
+
category: category || undefined,
|
|
81
|
+
tier: tier || undefined,
|
|
82
|
+
limit,
|
|
83
|
+
});
|
|
84
|
+
return json(res, { skills });
|
|
85
|
+
}
|
|
86
|
+
// GET /api/skills/search — Full-text skill search
|
|
87
|
+
if (pathname === "/api/skills/search" && method === "GET") {
|
|
88
|
+
const q = url.searchParams.get("q") || "";
|
|
89
|
+
const tier = url.searchParams.get("tier");
|
|
90
|
+
const category = url.searchParams.get("category");
|
|
91
|
+
const skills = searchSkills(q, {
|
|
92
|
+
tier: tier || undefined,
|
|
93
|
+
category: category || undefined,
|
|
94
|
+
});
|
|
95
|
+
return json(res, { skills });
|
|
96
|
+
}
|
|
97
|
+
// GET /api/skills/categories — List categories with counts
|
|
98
|
+
if (pathname === "/api/skills/categories" && method === "GET") {
|
|
99
|
+
const categories = getSkillCategories();
|
|
100
|
+
return json(res, { categories });
|
|
101
|
+
}
|
|
102
|
+
// GET /api/config — Get current config status (redacted)
|
|
103
|
+
if (pathname === "/api/config" && method === "GET") {
|
|
104
|
+
const config = loadConfig();
|
|
105
|
+
if (!config) {
|
|
106
|
+
return json(res, { configured: false });
|
|
107
|
+
}
|
|
108
|
+
return json(res, {
|
|
109
|
+
configured: true,
|
|
110
|
+
provider: config.provider,
|
|
111
|
+
model: config.model || "default",
|
|
112
|
+
hasApiKey: !!config.apiKey,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// POST /api/config — Update LLM config
|
|
116
|
+
if (pathname === "/api/config" && method === "POST") {
|
|
117
|
+
const body = JSON.parse(await readBody(req));
|
|
118
|
+
if (!body.provider)
|
|
119
|
+
return error(res, "Missing 'provider' field");
|
|
120
|
+
if (!body.apiKey) {
|
|
121
|
+
return error(res, "API key required");
|
|
122
|
+
}
|
|
123
|
+
saveConfig(body);
|
|
124
|
+
return json(res, { ok: true });
|
|
125
|
+
}
|
|
126
|
+
// GET /api/history — Get processing history
|
|
127
|
+
if (pathname === "/api/history" && method === "GET") {
|
|
128
|
+
return json(res, { history: getHistory() });
|
|
129
|
+
}
|
|
130
|
+
// GET /api/state — Get UserState (active packs, preferences)
|
|
131
|
+
if (pathname === "/api/state" && method === "GET") {
|
|
132
|
+
return json(res, loadUserState());
|
|
133
|
+
}
|
|
134
|
+
// POST /api/state/activate — Activate a skill pack
|
|
135
|
+
if (pathname === "/api/state/activate" && method === "POST") {
|
|
136
|
+
const body = JSON.parse(await readBody(req));
|
|
137
|
+
if (!body.pack_id)
|
|
138
|
+
return error(res, "Missing 'pack_id'");
|
|
139
|
+
const state = activatePack(body.pack_id);
|
|
140
|
+
return json(res, { ok: true, activePacks: state.activePacks });
|
|
141
|
+
}
|
|
142
|
+
// POST /api/state/deactivate — Deactivate a skill pack
|
|
143
|
+
if (pathname === "/api/state/deactivate" && method === "POST") {
|
|
144
|
+
const body = JSON.parse(await readBody(req));
|
|
145
|
+
if (!body.pack_id)
|
|
146
|
+
return error(res, "Missing 'pack_id'");
|
|
147
|
+
const state = deactivatePack(body.pack_id);
|
|
148
|
+
return json(res, { ok: true, activePacks: state.activePacks });
|
|
149
|
+
}
|
|
150
|
+
// GET /api/packs — List all available packs with metadata
|
|
151
|
+
if (pathname === "/api/packs" && method === "GET") {
|
|
152
|
+
// Import registry dynamically to avoid circular deps at module load
|
|
153
|
+
const { listPacks } = await import("../tools/registry.js");
|
|
154
|
+
const state = loadUserState();
|
|
155
|
+
return json(res, { packs: listPacks(state.activePacks) });
|
|
156
|
+
}
|
|
157
|
+
// 404 for unknown API routes
|
|
158
|
+
error(res, "Not found", 404);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
162
|
+
error(res, message, 500);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI subcommands for context extraction.
|
|
3
|
+
*
|
|
4
|
+
* npx @crowdlisten/harness context # Launch web UI
|
|
5
|
+
* npx @crowdlisten/harness context <file> # CLI-only processing
|
|
6
|
+
* npx @crowdlisten/harness setup # Configure LLM provider (handled in index.ts)
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Interactive setup: configure LLM provider and API key.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runSetupContext(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Process a file via CLI (no web UI).
|
|
14
|
+
*/
|
|
15
|
+
export declare function runContextCLI(filePath: string): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Launch the web UI server.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runContextWeb(): Promise<void>;
|