harper-kb 0.2.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/LICENSE +21 -0
- package/README.md +288 -0
- package/config.yaml +13 -0
- package/dist/core/embeddings.d.ts +31 -0
- package/dist/core/embeddings.d.ts.map +1 -0
- package/dist/core/embeddings.js +199 -0
- package/dist/core/embeddings.js.map +1 -0
- package/dist/core/entries.d.ts +101 -0
- package/dist/core/entries.d.ts.map +1 -0
- package/dist/core/entries.js +304 -0
- package/dist/core/entries.js.map +1 -0
- package/dist/core/history.d.ts +31 -0
- package/dist/core/history.d.ts.map +1 -0
- package/dist/core/history.js +119 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/knowledge-base.d.ts +49 -0
- package/dist/core/knowledge-base.d.ts.map +1 -0
- package/dist/core/knowledge-base.js +117 -0
- package/dist/core/knowledge-base.js.map +1 -0
- package/dist/core/search.d.ts +34 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +327 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/tags.d.ts +39 -0
- package/dist/core/tags.d.ts.map +1 -0
- package/dist/core/tags.js +97 -0
- package/dist/core/tags.js.map +1 -0
- package/dist/core/triage.d.ts +61 -0
- package/dist/core/triage.d.ts.map +1 -0
- package/dist/core/triage.js +136 -0
- package/dist/core/triage.js.map +1 -0
- package/dist/core/webhook-endpoints.d.ts +46 -0
- package/dist/core/webhook-endpoints.d.ts.map +1 -0
- package/dist/core/webhook-endpoints.js +85 -0
- package/dist/core/webhook-endpoints.js.map +1 -0
- package/dist/hooks.d.ts +67 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +53 -0
- package/dist/hooks.js.map +1 -0
- package/dist/http-utils.d.ts +38 -0
- package/dist/http-utils.d.ts.map +1 -0
- package/dist/http-utils.js +133 -0
- package/dist/http-utils.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/protocol.d.ts +25 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +105 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +28 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +706 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/oauth/authorize.d.ts +28 -0
- package/dist/oauth/authorize.d.ts.map +1 -0
- package/dist/oauth/authorize.js +421 -0
- package/dist/oauth/authorize.js.map +1 -0
- package/dist/oauth/init.d.ts +18 -0
- package/dist/oauth/init.d.ts.map +1 -0
- package/dist/oauth/init.js +30 -0
- package/dist/oauth/init.js.map +1 -0
- package/dist/oauth/keys.d.ts +34 -0
- package/dist/oauth/keys.d.ts.map +1 -0
- package/dist/oauth/keys.js +101 -0
- package/dist/oauth/keys.js.map +1 -0
- package/dist/oauth/metadata.d.ts +23 -0
- package/dist/oauth/metadata.d.ts.map +1 -0
- package/dist/oauth/metadata.js +57 -0
- package/dist/oauth/metadata.js.map +1 -0
- package/dist/oauth/middleware.d.ts +23 -0
- package/dist/oauth/middleware.d.ts.map +1 -0
- package/dist/oauth/middleware.js +65 -0
- package/dist/oauth/middleware.js.map +1 -0
- package/dist/oauth/register.d.ts +15 -0
- package/dist/oauth/register.d.ts.map +1 -0
- package/dist/oauth/register.js +78 -0
- package/dist/oauth/register.js.map +1 -0
- package/dist/oauth/token.d.ts +16 -0
- package/dist/oauth/token.d.ts.map +1 -0
- package/dist/oauth/token.js +184 -0
- package/dist/oauth/token.js.map +1 -0
- package/dist/oauth/validate.d.ts +40 -0
- package/dist/oauth/validate.d.ts.map +1 -0
- package/dist/oauth/validate.js +61 -0
- package/dist/oauth/validate.js.map +1 -0
- package/dist/resources/HistoryResource.d.ts +41 -0
- package/dist/resources/HistoryResource.d.ts.map +1 -0
- package/dist/resources/HistoryResource.js +61 -0
- package/dist/resources/HistoryResource.js.map +1 -0
- package/dist/resources/KnowledgeBaseResource.d.ts +60 -0
- package/dist/resources/KnowledgeBaseResource.d.ts.map +1 -0
- package/dist/resources/KnowledgeBaseResource.js +118 -0
- package/dist/resources/KnowledgeBaseResource.js.map +1 -0
- package/dist/resources/KnowledgeEntryResource.d.ts +61 -0
- package/dist/resources/KnowledgeEntryResource.d.ts.map +1 -0
- package/dist/resources/KnowledgeEntryResource.js +191 -0
- package/dist/resources/KnowledgeEntryResource.js.map +1 -0
- package/dist/resources/MeResource.d.ts +31 -0
- package/dist/resources/MeResource.d.ts.map +1 -0
- package/dist/resources/MeResource.js +40 -0
- package/dist/resources/MeResource.js.map +1 -0
- package/dist/resources/QueryLogResource.d.ts +22 -0
- package/dist/resources/QueryLogResource.d.ts.map +1 -0
- package/dist/resources/QueryLogResource.js +66 -0
- package/dist/resources/QueryLogResource.js.map +1 -0
- package/dist/resources/ServiceKeyResource.d.ts +52 -0
- package/dist/resources/ServiceKeyResource.d.ts.map +1 -0
- package/dist/resources/ServiceKeyResource.js +151 -0
- package/dist/resources/ServiceKeyResource.js.map +1 -0
- package/dist/resources/TagResource.d.ts +27 -0
- package/dist/resources/TagResource.d.ts.map +1 -0
- package/dist/resources/TagResource.js +41 -0
- package/dist/resources/TagResource.js.map +1 -0
- package/dist/resources/TriageResource.d.ts +53 -0
- package/dist/resources/TriageResource.d.ts.map +1 -0
- package/dist/resources/TriageResource.js +120 -0
- package/dist/resources/TriageResource.js.map +1 -0
- package/dist/resources/WebhookEndpointResource.d.ts +63 -0
- package/dist/resources/WebhookEndpointResource.d.ts.map +1 -0
- package/dist/resources/WebhookEndpointResource.js +115 -0
- package/dist/resources/WebhookEndpointResource.js.map +1 -0
- package/dist/types.d.ts +378 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks/github.d.ts +25 -0
- package/dist/webhooks/github.d.ts.map +1 -0
- package/dist/webhooks/github.js +165 -0
- package/dist/webhooks/github.js.map +1 -0
- package/dist/webhooks/middleware.d.ts +19 -0
- package/dist/webhooks/middleware.d.ts.map +1 -0
- package/dist/webhooks/middleware.js +144 -0
- package/dist/webhooks/middleware.js.map +1 -0
- package/dist/webhooks/types.d.ts +18 -0
- package/dist/webhooks/types.d.ts.map +1 -0
- package/dist/webhooks/types.js +5 -0
- package/dist/webhooks/types.js.map +1 -0
- package/package.json +69 -0
- package/schema/knowledge.graphql +136 -0
- package/schema/oauth.graphql +45 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage Queue Management
|
|
3
|
+
*
|
|
4
|
+
* Handles the intake queue for new knowledge submissions from webhooks
|
|
5
|
+
* and other sources. Items go through pending -> processing -> accepted/dismissed.
|
|
6
|
+
*
|
|
7
|
+
* All operations are scoped by kbId for multi-tenant isolation.
|
|
8
|
+
*/
|
|
9
|
+
import crypto from 'node:crypto';
|
|
10
|
+
import { createEntry } from "./entries.js";
|
|
11
|
+
/**
|
|
12
|
+
* Submit a new item to the triage queue.
|
|
13
|
+
*
|
|
14
|
+
* @param kbId - Knowledge base identifier
|
|
15
|
+
* @param source - Source identifier (e.g., "github-webhook", "slack-bot", "manual")
|
|
16
|
+
* @param summary - Brief summary of the knowledge to triage
|
|
17
|
+
* @param rawPayload - Original raw payload from the source
|
|
18
|
+
* @param sourceId - Optional deduplication key from the source system
|
|
19
|
+
* @returns The created triage item
|
|
20
|
+
*/
|
|
21
|
+
export async function submitTriage(kbId, source, summary, rawPayload, sourceId) {
|
|
22
|
+
const item = {
|
|
23
|
+
id: crypto.randomUUID(),
|
|
24
|
+
kbId,
|
|
25
|
+
source,
|
|
26
|
+
summary,
|
|
27
|
+
rawPayload: rawPayload || null,
|
|
28
|
+
status: 'pending',
|
|
29
|
+
sourceId: sourceId || undefined,
|
|
30
|
+
};
|
|
31
|
+
await databases.kb.TriageItem.put(item);
|
|
32
|
+
logger?.info?.(`Triage item submitted: ${item.id} from ${source}`);
|
|
33
|
+
return item;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Find a triage item by its source-specific ID for deduplication.
|
|
37
|
+
*
|
|
38
|
+
* @param kbId - Knowledge base identifier
|
|
39
|
+
* @param sourceId - The source-specific identifier
|
|
40
|
+
* @returns The matching triage item, or null if not found
|
|
41
|
+
*/
|
|
42
|
+
export async function findBySourceId(kbId, sourceId) {
|
|
43
|
+
for await (const item of databases.kb.TriageItem.search({
|
|
44
|
+
conditions: [
|
|
45
|
+
{ attribute: 'kbId', comparator: 'equals', value: kbId },
|
|
46
|
+
{ attribute: 'sourceId', comparator: 'equals', value: sourceId },
|
|
47
|
+
],
|
|
48
|
+
limit: 1,
|
|
49
|
+
})) {
|
|
50
|
+
return item;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Process a triage item with the given action.
|
|
56
|
+
*
|
|
57
|
+
* - "accepted": Optionally creates a new knowledge entry from provided data
|
|
58
|
+
* - "dismissed": Marks the item as dismissed
|
|
59
|
+
* - "linked": Links the triage item to an existing knowledge entry
|
|
60
|
+
*
|
|
61
|
+
* @param id - Triage item ID
|
|
62
|
+
* @param action - Action to take
|
|
63
|
+
* @param processedBy - Who processed this item
|
|
64
|
+
* @param options - Additional options (entry data for accept, linked entry ID)
|
|
65
|
+
* @returns The updated triage item
|
|
66
|
+
* @throws Error if the triage item does not exist
|
|
67
|
+
*/
|
|
68
|
+
export async function processTriage(id, action, processedBy, options) {
|
|
69
|
+
const existing = await databases.kb.TriageItem.get(id);
|
|
70
|
+
if (!existing) {
|
|
71
|
+
throw new Error(`Triage item not found: ${id}`);
|
|
72
|
+
}
|
|
73
|
+
const item = existing;
|
|
74
|
+
const now = new Date();
|
|
75
|
+
// Update common fields
|
|
76
|
+
item.status = action;
|
|
77
|
+
item.action = action;
|
|
78
|
+
item.processedBy = processedBy;
|
|
79
|
+
item.processedAt = now;
|
|
80
|
+
// Handle action-specific logic
|
|
81
|
+
if (action === 'accepted' && options?.entryData) {
|
|
82
|
+
// Create a new knowledge entry from the triage data
|
|
83
|
+
const entryData = {
|
|
84
|
+
...options.entryData,
|
|
85
|
+
kbId: item.kbId,
|
|
86
|
+
source: options.entryData.source || item.source,
|
|
87
|
+
addedBy: options.entryData.addedBy || processedBy,
|
|
88
|
+
};
|
|
89
|
+
const entry = await createEntry(entryData);
|
|
90
|
+
item.draftEntryId = entry.id;
|
|
91
|
+
}
|
|
92
|
+
else if (action === 'accepted' && options?.linkedEntryId) {
|
|
93
|
+
// Entry was created externally (e.g., web UI created it first) — link it
|
|
94
|
+
item.draftEntryId = options.linkedEntryId;
|
|
95
|
+
}
|
|
96
|
+
else if (action === 'linked' && options?.linkedEntryId) {
|
|
97
|
+
// Link to an existing knowledge entry
|
|
98
|
+
item.matchedEntryId = options.linkedEntryId;
|
|
99
|
+
}
|
|
100
|
+
// Store the updated triage item
|
|
101
|
+
await databases.kb.TriageItem.put(item);
|
|
102
|
+
logger?.info?.(`Triage item ${id} processed: ${action} by ${processedBy}`);
|
|
103
|
+
return item;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* List pending triage items for a specific knowledge base.
|
|
107
|
+
*
|
|
108
|
+
* @param kbId - Knowledge base identifier
|
|
109
|
+
* @param limit - Maximum number of items to return (default 200)
|
|
110
|
+
* @returns Array of triage items with status "pending"
|
|
111
|
+
*/
|
|
112
|
+
export async function listPending(kbId, limit = 200) {
|
|
113
|
+
const results = [];
|
|
114
|
+
for await (const item of databases.kb.TriageItem.search({
|
|
115
|
+
conditions: [
|
|
116
|
+
{ attribute: 'kbId', comparator: 'equals', value: kbId },
|
|
117
|
+
{ attribute: 'status', comparator: 'equals', value: 'pending' },
|
|
118
|
+
],
|
|
119
|
+
limit,
|
|
120
|
+
})) {
|
|
121
|
+
results.push(item);
|
|
122
|
+
}
|
|
123
|
+
return results;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Dismiss a triage item.
|
|
127
|
+
*
|
|
128
|
+
* Convenience method that calls processTriage with action "dismissed".
|
|
129
|
+
*
|
|
130
|
+
* @param id - Triage item ID
|
|
131
|
+
* @param processedBy - Who dismissed this item
|
|
132
|
+
*/
|
|
133
|
+
export async function dismissTriage(id, processedBy) {
|
|
134
|
+
await processTriage(id, 'dismissed', processedBy);
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=triage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triage.js","sourceRoot":"","sources":["../../src/core/triage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Endpoint Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for per-KB webhook endpoints. Each endpoint has a
|
|
5
|
+
* randomly generated secret that is embedded in the webhook URL and
|
|
6
|
+
* also serves as the HMAC signing key for GitHub payload verification.
|
|
7
|
+
*
|
|
8
|
+
* The secret is never stored in plaintext — only its SHA-256 hash is
|
|
9
|
+
* persisted as the record's primary key for O(1) lookup.
|
|
10
|
+
*/
|
|
11
|
+
import type { WebhookEndpoint } from '../types.ts';
|
|
12
|
+
/**
|
|
13
|
+
* Hash a webhook secret to produce the record ID.
|
|
14
|
+
* SHA-256 is sufficient — these are high-entropy random tokens, not passwords.
|
|
15
|
+
*/
|
|
16
|
+
export declare function hashSecret(secret: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Create a new webhook endpoint for a KB.
|
|
19
|
+
*
|
|
20
|
+
* Generates a random secret, stores the SHA-256 hash as the record ID,
|
|
21
|
+
* and returns both the record and the plaintext secret (shown once).
|
|
22
|
+
*
|
|
23
|
+
* @throws If the KB does not exist
|
|
24
|
+
*/
|
|
25
|
+
export declare function createWebhookEndpoint(kbId: string, provider: string, label?: string, createdBy?: string): Promise<{
|
|
26
|
+
endpoint: WebhookEndpoint;
|
|
27
|
+
secret: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Validate a webhook secret against a KB and provider.
|
|
31
|
+
*
|
|
32
|
+
* Hashes the secret, looks up the record, and verifies the kbId and
|
|
33
|
+
* provider match. Returns the record if valid, null otherwise.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateWebhookSecret(secret: string, kbId: string, provider: string): Promise<WebhookEndpoint | null>;
|
|
36
|
+
/**
|
|
37
|
+
* List all webhook endpoints for a KB.
|
|
38
|
+
*/
|
|
39
|
+
export declare function listWebhookEndpoints(kbId: string): Promise<WebhookEndpoint[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Delete a webhook endpoint.
|
|
42
|
+
*
|
|
43
|
+
* @throws If the endpoint does not exist or belongs to a different KB
|
|
44
|
+
*/
|
|
45
|
+
export declare function deleteWebhookEndpoint(id: string, kbId: string): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=webhook-endpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-endpoints.d.ts","sourceRoot":"","sources":["../../src/core/webhook-endpoints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBxD;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAC1C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CASjC;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAQnF;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Endpoint Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for per-KB webhook endpoints. Each endpoint has a
|
|
5
|
+
* randomly generated secret that is embedded in the webhook URL and
|
|
6
|
+
* also serves as the HMAC signing key for GitHub payload verification.
|
|
7
|
+
*
|
|
8
|
+
* The secret is never stored in plaintext — only its SHA-256 hash is
|
|
9
|
+
* persisted as the record's primary key for O(1) lookup.
|
|
10
|
+
*/
|
|
11
|
+
import crypto from 'node:crypto';
|
|
12
|
+
/**
|
|
13
|
+
* Hash a webhook secret to produce the record ID.
|
|
14
|
+
* SHA-256 is sufficient — these are high-entropy random tokens, not passwords.
|
|
15
|
+
*/
|
|
16
|
+
export function hashSecret(secret) {
|
|
17
|
+
return crypto.createHash('sha256').update(secret).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a new webhook endpoint for a KB.
|
|
21
|
+
*
|
|
22
|
+
* Generates a random secret, stores the SHA-256 hash as the record ID,
|
|
23
|
+
* and returns both the record and the plaintext secret (shown once).
|
|
24
|
+
*
|
|
25
|
+
* @throws If the KB does not exist
|
|
26
|
+
*/
|
|
27
|
+
export async function createWebhookEndpoint(kbId, provider, label, createdBy) {
|
|
28
|
+
// Verify KB exists
|
|
29
|
+
const kb = await databases.kb.KnowledgeBase.get(kbId);
|
|
30
|
+
if (!kb) {
|
|
31
|
+
throw new Error(`Knowledge base "${kbId}" not found`);
|
|
32
|
+
}
|
|
33
|
+
const secret = crypto.randomBytes(32).toString('base64url');
|
|
34
|
+
const id = hashSecret(secret);
|
|
35
|
+
const endpoint = {
|
|
36
|
+
id,
|
|
37
|
+
kbId,
|
|
38
|
+
provider,
|
|
39
|
+
label: label || undefined,
|
|
40
|
+
createdBy: createdBy || undefined,
|
|
41
|
+
};
|
|
42
|
+
await databases.kb.WebhookEndpoint.put(endpoint);
|
|
43
|
+
return { endpoint, secret };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validate a webhook secret against a KB and provider.
|
|
47
|
+
*
|
|
48
|
+
* Hashes the secret, looks up the record, and verifies the kbId and
|
|
49
|
+
* provider match. Returns the record if valid, null otherwise.
|
|
50
|
+
*/
|
|
51
|
+
export async function validateWebhookSecret(secret, kbId, provider) {
|
|
52
|
+
const id = hashSecret(secret);
|
|
53
|
+
const record = await databases.kb.WebhookEndpoint.get(id);
|
|
54
|
+
if (!record)
|
|
55
|
+
return null;
|
|
56
|
+
const endpoint = record;
|
|
57
|
+
if (endpoint.kbId !== kbId || endpoint.provider !== provider)
|
|
58
|
+
return null;
|
|
59
|
+
return endpoint;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* List all webhook endpoints for a KB.
|
|
63
|
+
*/
|
|
64
|
+
export async function listWebhookEndpoints(kbId) {
|
|
65
|
+
const results = [];
|
|
66
|
+
for await (const item of databases.kb.WebhookEndpoint.search({
|
|
67
|
+
conditions: [{ attribute: 'kbId', comparator: 'equals', value: kbId }],
|
|
68
|
+
})) {
|
|
69
|
+
results.push(item);
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Delete a webhook endpoint.
|
|
75
|
+
*
|
|
76
|
+
* @throws If the endpoint does not exist or belongs to a different KB
|
|
77
|
+
*/
|
|
78
|
+
export async function deleteWebhookEndpoint(id, kbId) {
|
|
79
|
+
const record = await databases.kb.WebhookEndpoint.get(id);
|
|
80
|
+
if (!record || record.kbId !== kbId) {
|
|
81
|
+
throw new Error('Webhook endpoint not found');
|
|
82
|
+
}
|
|
83
|
+
await databases.kb.WebhookEndpoint.delete(id);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=webhook-endpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-endpoints.js","sourceRoot":"","sources":["../../src/core/webhook-endpoints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACxC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,IAAY,EACZ,QAAgB,EAChB,KAAc,EACd,SAAkB;IAElB,mBAAmB;IACnB,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,aAAa,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAE9B,MAAM,QAAQ,GAAoB;QACjC,EAAE;QACF,IAAI;QACJ,QAAQ;QACR,KAAK,EAAE,KAAK,IAAI,SAAS;QACzB,SAAS,EAAE,SAAS,IAAI,SAAS;KACjC,CAAC;IAEF,MAAM,SAAS,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,QAA8C,CAAC,CAAC;IAEvF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,MAAc,EACd,IAAY,EACZ,QAAgB;IAEhB,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,QAAQ,GAAG,MAAoC,CAAC;IACtD,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE1E,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAY;IACtD,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,SAAS,CAAC,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC;QAC5D,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KACtE,CAAC,EAAE,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,IAAkC,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAU,EAAE,IAAY;IACnE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,IAAK,MAAqC,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,SAAS,CAAC,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAC/C,CAAC"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Hooks
|
|
3
|
+
*
|
|
4
|
+
* Exposes extension points that the parent application (or other plugins)
|
|
5
|
+
* can wire into. Follows the same pattern as @harperfast/oauth's registerHooks.
|
|
6
|
+
*
|
|
7
|
+
* Usage from the parent app:
|
|
8
|
+
*
|
|
9
|
+
* import { registerHooks } from 'harper-kb';
|
|
10
|
+
* registerHooks({
|
|
11
|
+
* onAccessCheck: async (caller, kbId) => {
|
|
12
|
+
* // custom authorization logic
|
|
13
|
+
* return { allow: true };
|
|
14
|
+
* },
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
import type { ValidatedCaller } from './oauth/validate.ts';
|
|
18
|
+
export interface AccessCheckResult {
|
|
19
|
+
/** Whether to allow access */
|
|
20
|
+
allow: boolean;
|
|
21
|
+
/** Override the caller's scopes (e.g., downgrade to read-only) */
|
|
22
|
+
scopes?: string[];
|
|
23
|
+
/** Reason for denial (logged, not exposed to client) */
|
|
24
|
+
reason?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface KnowledgeHooks {
|
|
27
|
+
/**
|
|
28
|
+
* Called after JWT validation, before the MCP request is processed.
|
|
29
|
+
* Return { allow: false } to deny access (results in 403).
|
|
30
|
+
* Return { allow: true, scopes: [...] } to override granted scopes.
|
|
31
|
+
* If not registered, all authenticated users are allowed.
|
|
32
|
+
*/
|
|
33
|
+
onAccessCheck?: (caller: ValidatedCaller, kbId: string) => Promise<AccessCheckResult>;
|
|
34
|
+
/**
|
|
35
|
+
* URL path for login redirect.
|
|
36
|
+
*
|
|
37
|
+
* Single provider: "/oauth/github/login" — goes straight to that provider
|
|
38
|
+
* Multiple providers: "/login" — goes to the app's provider-selection page
|
|
39
|
+
* Not set: credential-only login (no SSO button)
|
|
40
|
+
*
|
|
41
|
+
* The plugin appends ?redirect=... so the login page knows where to return.
|
|
42
|
+
*/
|
|
43
|
+
loginPath?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Register hook callbacks for the knowledge base plugin.
|
|
47
|
+
*
|
|
48
|
+
* Can be called multiple times — later calls merge with (and override)
|
|
49
|
+
* previously registered hooks.
|
|
50
|
+
*/
|
|
51
|
+
export declare function registerHooks(newHooks: KnowledgeHooks): void;
|
|
52
|
+
/**
|
|
53
|
+
* Run the onAccessCheck hook if registered.
|
|
54
|
+
*
|
|
55
|
+
* Returns null if no hook is registered (caller is allowed by default).
|
|
56
|
+
* Returns the AccessCheckResult otherwise.
|
|
57
|
+
*/
|
|
58
|
+
export declare function checkAccess(caller: ValidatedCaller, kbId: string): Promise<AccessCheckResult | null>;
|
|
59
|
+
/**
|
|
60
|
+
* Get the configured login path (if any).
|
|
61
|
+
*/
|
|
62
|
+
export declare function getLoginPath(): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Reset all hooks (for testing).
|
|
65
|
+
*/
|
|
66
|
+
export declare function _resetHooks(): void;
|
|
67
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAM3D,MAAM,WAAW,iBAAiB;IACjC,8BAA8B;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAEtF;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAE5D;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAG1G;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Hooks
|
|
3
|
+
*
|
|
4
|
+
* Exposes extension points that the parent application (or other plugins)
|
|
5
|
+
* can wire into. Follows the same pattern as @harperfast/oauth's registerHooks.
|
|
6
|
+
*
|
|
7
|
+
* Usage from the parent app:
|
|
8
|
+
*
|
|
9
|
+
* import { registerHooks } from 'harper-kb';
|
|
10
|
+
* registerHooks({
|
|
11
|
+
* onAccessCheck: async (caller, kbId) => {
|
|
12
|
+
* // custom authorization logic
|
|
13
|
+
* return { allow: true };
|
|
14
|
+
* },
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Hook Registry (module-level singleton)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
let hooks = {};
|
|
21
|
+
/**
|
|
22
|
+
* Register hook callbacks for the knowledge base plugin.
|
|
23
|
+
*
|
|
24
|
+
* Can be called multiple times — later calls merge with (and override)
|
|
25
|
+
* previously registered hooks.
|
|
26
|
+
*/
|
|
27
|
+
export function registerHooks(newHooks) {
|
|
28
|
+
hooks = { ...hooks, ...newHooks };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run the onAccessCheck hook if registered.
|
|
32
|
+
*
|
|
33
|
+
* Returns null if no hook is registered (caller is allowed by default).
|
|
34
|
+
* Returns the AccessCheckResult otherwise.
|
|
35
|
+
*/
|
|
36
|
+
export async function checkAccess(caller, kbId) {
|
|
37
|
+
if (!hooks.onAccessCheck)
|
|
38
|
+
return null;
|
|
39
|
+
return hooks.onAccessCheck(caller, kbId);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the configured login path (if any).
|
|
43
|
+
*/
|
|
44
|
+
export function getLoginPath() {
|
|
45
|
+
return hooks.loginPath || null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reset all hooks (for testing).
|
|
49
|
+
*/
|
|
50
|
+
export function _resetHooks() {
|
|
51
|
+
hooks = {};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAsCH,+EAA+E;AAC/E,yCAAyC;AACzC,+EAA+E;AAE/E,IAAI,KAAK,GAAmB,EAAE,CAAC;AAE/B;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAwB;IACrD,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAuB,EAAE,IAAY;IACtE,IAAI,CAAC,KAAK,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY;IAC3B,OAAO,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IAC1B,KAAK,GAAG,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Utilities for Harper's stream-based request/response handling.
|
|
3
|
+
*
|
|
4
|
+
* Shared by MCP middleware, OAuth middleware, and webhook middleware.
|
|
5
|
+
*/
|
|
6
|
+
import type { HarperRequest } from './types.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Read the request body as a string from Harper's stream-based body.
|
|
9
|
+
*
|
|
10
|
+
* Harper's request.body is a RequestBody wrapper with .on()/.pipe() methods,
|
|
11
|
+
* not a parsed object. We need to consume the stream to get the raw text.
|
|
12
|
+
*
|
|
13
|
+
* Enforces a maximum body size to prevent memory exhaustion from oversized requests.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readBody(request: HarperRequest): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Build Web Standard Headers from Harper's Headers object.
|
|
18
|
+
*
|
|
19
|
+
* Harper's request.headers is a custom Headers class (iterable, with .get()),
|
|
20
|
+
* not a plain Record<string, string>.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildHeaders(request: HarperRequest): Headers;
|
|
23
|
+
/**
|
|
24
|
+
* Build the base URL (origin) from a Harper request.
|
|
25
|
+
*
|
|
26
|
+
* Uses the request's protocol and host properties. Defaults to
|
|
27
|
+
* http://localhost:9926 if not available.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getBaseUrl(request: HarperRequest): string;
|
|
30
|
+
/**
|
|
31
|
+
* Parse an application/x-www-form-urlencoded body into a key-value map.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseFormBody(body: string): Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Get a header value from Harper's request, case-insensitive.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getHeader(request: HarperRequest, name: string): string;
|
|
38
|
+
//# sourceMappingURL=http-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-utils.d.ts","sourceRoot":"","sources":["../src/http-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAwChE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAsB5D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAIzD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOlE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA2BtE"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Utilities for Harper's stream-based request/response handling.
|
|
3
|
+
*
|
|
4
|
+
* Shared by MCP middleware, OAuth middleware, and webhook middleware.
|
|
5
|
+
*/
|
|
6
|
+
/** Maximum request body size: 1 MB */
|
|
7
|
+
const MAX_BODY_SIZE = 1_048_576;
|
|
8
|
+
/**
|
|
9
|
+
* Read the request body as a string from Harper's stream-based body.
|
|
10
|
+
*
|
|
11
|
+
* Harper's request.body is a RequestBody wrapper with .on()/.pipe() methods,
|
|
12
|
+
* not a parsed object. We need to consume the stream to get the raw text.
|
|
13
|
+
*
|
|
14
|
+
* Enforces a maximum body size to prevent memory exhaustion from oversized requests.
|
|
15
|
+
*/
|
|
16
|
+
export function readBody(request) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const body = request.body;
|
|
19
|
+
if (!body) {
|
|
20
|
+
resolve('');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// If body is already a string (unlikely but handle it)
|
|
24
|
+
if (typeof body === 'string') {
|
|
25
|
+
resolve(body);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// If body is a stream with .on(), read it
|
|
29
|
+
if (typeof body.on === 'function') {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let totalSize = 0;
|
|
32
|
+
body.on('data', (chunk) => {
|
|
33
|
+
totalSize += chunk.length;
|
|
34
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
35
|
+
body.destroy?.();
|
|
36
|
+
reject(new Error('Request body too large'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
chunks.push(Buffer.from(chunk));
|
|
40
|
+
});
|
|
41
|
+
body.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
42
|
+
body.on('error', reject);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// If body is already an object (parsed), stringify it
|
|
46
|
+
if (typeof body === 'object') {
|
|
47
|
+
resolve(JSON.stringify(body));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
resolve(String(body));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build Web Standard Headers from Harper's Headers object.
|
|
55
|
+
*
|
|
56
|
+
* Harper's request.headers is a custom Headers class (iterable, with .get()),
|
|
57
|
+
* not a plain Record<string, string>.
|
|
58
|
+
*/
|
|
59
|
+
export function buildHeaders(request) {
|
|
60
|
+
const headers = new Headers();
|
|
61
|
+
const src = request.headers;
|
|
62
|
+
if (!src)
|
|
63
|
+
return headers;
|
|
64
|
+
// Harper's Headers class is iterable with [key, value] pairs
|
|
65
|
+
if (typeof src[Symbol.iterator] === 'function') {
|
|
66
|
+
for (const [key, value] of src) {
|
|
67
|
+
if (value !== undefined) {
|
|
68
|
+
headers.set(key, Array.isArray(value) ? value.join(', ') : String(value));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (typeof src === 'object') {
|
|
73
|
+
// Fallback: plain object
|
|
74
|
+
for (const [key, value] of Object.entries(src)) {
|
|
75
|
+
if (value !== undefined) {
|
|
76
|
+
headers.set(key, Array.isArray(value) ? value.join(', ') : String(value));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return headers;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the base URL (origin) from a Harper request.
|
|
84
|
+
*
|
|
85
|
+
* Uses the request's protocol and host properties. Defaults to
|
|
86
|
+
* http://localhost:9926 if not available.
|
|
87
|
+
*/
|
|
88
|
+
export function getBaseUrl(request) {
|
|
89
|
+
const protocol = request.protocol || 'http';
|
|
90
|
+
const host = request.host || 'localhost:9926';
|
|
91
|
+
return `${protocol}://${host}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse an application/x-www-form-urlencoded body into a key-value map.
|
|
95
|
+
*/
|
|
96
|
+
export function parseFormBody(body) {
|
|
97
|
+
const params = new URLSearchParams(body);
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const [key, value] of params) {
|
|
100
|
+
result[key] = value;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a header value from Harper's request, case-insensitive.
|
|
106
|
+
*/
|
|
107
|
+
export function getHeader(request, name) {
|
|
108
|
+
const headers = request.headers;
|
|
109
|
+
if (!headers)
|
|
110
|
+
return '';
|
|
111
|
+
// Try .get() method (Harper's Headers class)
|
|
112
|
+
if (typeof headers.get === 'function') {
|
|
113
|
+
const val = headers.get(name);
|
|
114
|
+
if (val !== undefined && val !== null)
|
|
115
|
+
return String(val);
|
|
116
|
+
}
|
|
117
|
+
// Try direct access (case-sensitive)
|
|
118
|
+
const direct = headers[name] ?? headers[name.toLowerCase()];
|
|
119
|
+
if (direct !== undefined) {
|
|
120
|
+
return Array.isArray(direct) ? direct[0] : String(direct);
|
|
121
|
+
}
|
|
122
|
+
// Fallback: iterate to find case-insensitive match
|
|
123
|
+
const lowerName = name.toLowerCase();
|
|
124
|
+
if (typeof headers[Symbol.iterator] === 'function') {
|
|
125
|
+
for (const [key, value] of headers) {
|
|
126
|
+
if (key.toLowerCase() === lowerName && value !== undefined) {
|
|
127
|
+
return Array.isArray(value) ? value[0] : String(value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=http-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-utils.js","sourceRoot":"","sources":["../src/http-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harper-kb — Harper Knowledge Base Plugin
|
|
3
|
+
*
|
|
4
|
+
* Sub-component plugin that provides a knowledge base with vector search,
|
|
5
|
+
* triage queue, and MCP server integration. Loaded by a parent application
|
|
6
|
+
* via `package:` in the parent's config.yaml.
|
|
7
|
+
*
|
|
8
|
+
* Harper calls handleApplication(scope) on each worker thread.
|
|
9
|
+
*/
|
|
10
|
+
import type { Scope } from './types.ts';
|
|
11
|
+
export { createKnowledgeBase, getKnowledgeBase, updateKnowledgeBase, deleteKnowledgeBase, listKnowledgeBases, } from './core/knowledge-base.ts';
|
|
12
|
+
export { createEntry, getEntry, updateEntry, deprecateEntry, linkSupersedes, linkSiblings, linkRelated, } from './core/entries.ts';
|
|
13
|
+
export { search, filterByApplicability } from './core/search.ts';
|
|
14
|
+
export { logEdit, getHistory } from './core/history.ts';
|
|
15
|
+
export { listTags, syncTags } from './core/tags.ts';
|
|
16
|
+
export { submitTriage, processTriage, listPending, dismissTriage, findBySourceId } from './core/triage.ts';
|
|
17
|
+
export { generateEmbedding, initEmbeddingModel, dispose as disposeEmbeddings } from './core/embeddings.ts';
|
|
18
|
+
export { createWebhookEndpoint, listWebhookEndpoints, deleteWebhookEndpoint } from './core/webhook-endpoints.ts';
|
|
19
|
+
export { registerHooks } from './hooks.ts';
|
|
20
|
+
export type { KnowledgeBase, KnowledgeBaseInput, KnowledgeBaseUpdate, KnowledgeEntry, KnowledgeEntryInput, KnowledgeEntryUpdate, KnowledgeEntryEdit, TriageItem, KnowledgeTag, QueryLog, ServiceKey, SearchParams, SearchResult, Reference, ApplicabilityScope, ApplicabilityContext, TriageAction, TriageProcessOptions, WebhookEndpoint, KnowledgePluginConfig, } from './types.ts';
|
|
21
|
+
export type { KnowledgeHooks, AccessCheckResult } from './hooks.ts';
|
|
22
|
+
export type { ValidatedCaller } from './oauth/validate.ts';
|
|
23
|
+
/**
|
|
24
|
+
* Plugin entry point — called by Harper on each worker thread.
|
|
25
|
+
*/
|
|
26
|
+
export declare function handleApplication(scope: Scope): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,OAAO,KAAK,EAAE,KAAK,EAAyB,MAAM,YAAY,CAAC;AAG/D,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,WAAW,EACX,QAAQ,EACR,WAAW,EACX,cAAc,EACd,cAAc,EACd,YAAY,EACZ,WAAW,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC3G,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC3G,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACjH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3C,YAAY,EACX,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,qBAAqB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA2DnE"}
|