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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nathan Heskew
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# harper-kb
|
|
2
|
+
|
|
3
|
+
Knowledge base for [Harper](https://harper.fast/), built on Harper, with MCP server integration.
|
|
4
|
+
|
|
5
|
+
A Harper plugin that provides searchable, scoped knowledge entries with vector embeddings for semantic search. Exposes a REST API and MCP endpoint.
|
|
6
|
+
|
|
7
|
+
## Consumers
|
|
8
|
+
|
|
9
|
+
- **Support team** — finding solutions, patterns, gotchas, customer edge cases
|
|
10
|
+
- **DX lab "Harper expert"** — backing knowledge for the AI expert role in Gas Town labs
|
|
11
|
+
- **Claude Code / IDE assistants** — Harper context via MCP without per-project CLAUDE.md files
|
|
12
|
+
- **Any MCP client** — Cursor, VS Code + Copilot, JetBrains, ChatGPT, Gemini, etc.
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### Prerequisites
|
|
17
|
+
|
|
18
|
+
- [Harper](https://harper.fast/) >= 4.7.0
|
|
19
|
+
- Node.js >= 22
|
|
20
|
+
|
|
21
|
+
### Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install harper-kb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Configure
|
|
28
|
+
|
|
29
|
+
Add to your application's `config.yaml`:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
'harper-kb':
|
|
33
|
+
package: 'harper-kb'
|
|
34
|
+
embeddingModel: nomic-embed-text # default
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Run
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
harperdb dev .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Embeddings
|
|
44
|
+
|
|
45
|
+
Vector embeddings for semantic search run locally on CPU using [Nomic](https://huggingface.co/nomic-ai) embedding models via llama.cpp. Two backends are supported:
|
|
46
|
+
|
|
47
|
+
| Backend | Install | Use case |
|
|
48
|
+
| ------------------------------------------------------------------------------ | ------------------------------------ | ---------------------------------------- |
|
|
49
|
+
| [harper-fabric-embeddings](https://github.com/heskew/harper-fabric-embeddings) | Optional dependency (auto-installed) | Production on Fabric (linux-x64, ~19 MB) |
|
|
50
|
+
| [node-llama-cpp](https://github.com/withcatai/node-llama-cpp) | `npm install node-llama-cpp` | Local development on any platform |
|
|
51
|
+
|
|
52
|
+
The plugin tries `harper-fabric-embeddings` first and falls back to `node-llama-cpp`. If neither is available, semantic search is skipped and keyword search still works.
|
|
53
|
+
|
|
54
|
+
### Models
|
|
55
|
+
|
|
56
|
+
| Config key | Model | Parameters | Dimensions |
|
|
57
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------- | ---------- | ---------- |
|
|
58
|
+
| `nomic-embed-text` (default) | [nomic-embed-text-v1.5-GGUF](https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF) | 137M | 768 |
|
|
59
|
+
| `nomic-embed-text-v2-moe` | [nomic-embed-text-v2-moe-GGUF](https://huggingface.co/nomic-ai/nomic-embed-text-v2-moe-GGUF) | 475M (MoE) | 768 |
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
'harper-kb':
|
|
63
|
+
package: 'harper-kb'
|
|
64
|
+
embeddingModel: nomic-embed-text # v1.5 (default)
|
|
65
|
+
# embeddingModel: nomic-embed-text-v2-moe # v2 MoE — better quality, larger
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Architecture
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
harper-kb
|
|
72
|
+
├── src/
|
|
73
|
+
│ ├── index.ts ← plugin entry: handleApplication()
|
|
74
|
+
│ ├── hooks.ts ← extensibility hooks (onAccessCheck, loginPath)
|
|
75
|
+
│ ├── http-utils.ts ← request body reading, header helpers
|
|
76
|
+
│ ├── types.ts ← shared types + Harper global declarations
|
|
77
|
+
│ ├── core/ ← shared logic
|
|
78
|
+
│ │ ├── embeddings.ts ← model download, init, vector generation
|
|
79
|
+
│ │ ├── entries.ts ← CRUD + relationship management
|
|
80
|
+
│ │ ├── history.ts ← edit history audit log
|
|
81
|
+
│ │ ├── knowledge-base.ts ← KB registry (multi-tenant)
|
|
82
|
+
│ │ ├── search.ts ← keyword / semantic / hybrid search
|
|
83
|
+
│ │ ├── tags.ts ← tag registry with counts
|
|
84
|
+
│ │ ├── triage.ts ← webhook intake queue
|
|
85
|
+
│ │ └── webhook-endpoints.ts ← webhook secret management
|
|
86
|
+
│ ├── resources/ ← REST Resource classes
|
|
87
|
+
│ │ ├── HistoryResource.ts
|
|
88
|
+
│ │ ├── KnowledgeBaseResource.ts
|
|
89
|
+
│ │ ├── KnowledgeEntryResource.ts
|
|
90
|
+
│ │ ├── MeResource.ts
|
|
91
|
+
│ │ ├── QueryLogResource.ts
|
|
92
|
+
│ │ ├── ServiceKeyResource.ts
|
|
93
|
+
│ │ ├── TagResource.ts
|
|
94
|
+
│ │ ├── TriageResource.ts
|
|
95
|
+
│ │ └── WebhookEndpointResource.ts
|
|
96
|
+
│ ├── mcp/ ← MCP server (JSON-RPC over HTTP)
|
|
97
|
+
│ │ ├── protocol.ts ← JSON-RPC dispatcher
|
|
98
|
+
│ │ ├── server.ts ← HTTP middleware (auth, KB scoping)
|
|
99
|
+
│ │ └── tools.ts ← tool definitions + handlers
|
|
100
|
+
│ ├── oauth/ ← OAuth 2.1 authorization server
|
|
101
|
+
│ │ ├── authorize.ts
|
|
102
|
+
│ │ ├── init.ts
|
|
103
|
+
│ │ ├── keys.ts
|
|
104
|
+
│ │ ├── metadata.ts
|
|
105
|
+
│ │ ├── middleware.ts
|
|
106
|
+
│ │ ├── register.ts
|
|
107
|
+
│ │ ├── token.ts
|
|
108
|
+
│ │ └── validate.ts
|
|
109
|
+
│ └── webhooks/ ← webhook intake (GitHub)
|
|
110
|
+
│ ├── github.ts
|
|
111
|
+
│ ├── middleware.ts
|
|
112
|
+
│ └── types.ts
|
|
113
|
+
├── schema/
|
|
114
|
+
│ ├── knowledge.graphql ← table definitions (database: "kb")
|
|
115
|
+
│ └── oauth.graphql ← OAuth tables
|
|
116
|
+
├── config.yaml
|
|
117
|
+
├── package.json
|
|
118
|
+
└── test/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Both REST and MCP run in the Harper process, both call the same core functions with zero overhead.
|
|
122
|
+
|
|
123
|
+
## REST API
|
|
124
|
+
|
|
125
|
+
| Endpoint | Method | Auth | Description |
|
|
126
|
+
| ----------------------- | --------------- | ---------- | ------------------------- |
|
|
127
|
+
| `/KnowledgeBase/` | GET | Public | List knowledge bases |
|
|
128
|
+
| `/KnowledgeBase/<id>` | GET | Public | Get KB by ID |
|
|
129
|
+
| `/KnowledgeBase/` | POST/PUT/DELETE | Team | Manage knowledge bases |
|
|
130
|
+
| `/Knowledge/<id>` | GET | Public | Get entry by ID |
|
|
131
|
+
| `/Knowledge/?query=...` | GET | Public | Search entries |
|
|
132
|
+
| `/Knowledge/` | POST | Required | Create entry |
|
|
133
|
+
| `/Knowledge/<id>` | PUT | Required | Update entry |
|
|
134
|
+
| `/Knowledge/<id>` | DELETE | Team | Deprecate entry |
|
|
135
|
+
| `/KnowledgeTag/` | GET | Public | List all tags |
|
|
136
|
+
| `/Triage/` | GET | Team | List pending triage items |
|
|
137
|
+
| `/Triage/` | POST | Service/AI | Submit triage item |
|
|
138
|
+
| `/Triage/<id>` | PUT | Team | Process triage item |
|
|
139
|
+
| `/QueryLog/` | GET | Team | Search analytics |
|
|
140
|
+
| `/ServiceKey/` | GET/POST/DELETE | Team | API key management |
|
|
141
|
+
| `/WebhookEndpoint/` | GET/POST/DELETE | Team | Webhook endpoint secrets |
|
|
142
|
+
| `/History/<entryId>` | GET | Public | Edit history for an entry |
|
|
143
|
+
| `/Me/` | GET | Public | Current user/session info |
|
|
144
|
+
|
|
145
|
+
### Search Parameters
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
GET /Knowledge/?query=MQTT+auth&tags=mqtt,config&limit=10&mode=keyword&context={"harper":"5.0","storageEngine":"lmdb"}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- `query` — search text (required)
|
|
152
|
+
- `tags` — comma-separated tag filter
|
|
153
|
+
- `limit` — max results (default 10)
|
|
154
|
+
- `mode` — `keyword`, `semantic`, or `hybrid` (default)
|
|
155
|
+
- `context` — JSON applicability context for result boosting
|
|
156
|
+
|
|
157
|
+
## MCP Endpoint
|
|
158
|
+
|
|
159
|
+
Each knowledge base gets its own MCP endpoint at `/mcp/<kbId>`. Connect any MCP-compatible client:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"mcpServers": {
|
|
164
|
+
"harper-kb": {
|
|
165
|
+
"url": "https://kb.harper.fast:9926/mcp/my-kb-id"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Tools
|
|
172
|
+
|
|
173
|
+
| Tool | Description |
|
|
174
|
+
| --------------------- | ----------------------------------------------------------------- |
|
|
175
|
+
| `knowledge_search` | Search with keyword/semantic/hybrid modes + applicability context |
|
|
176
|
+
| `knowledge_add` | Add a new entry (auto-tagged `ai-generated`) |
|
|
177
|
+
| `knowledge_get` | Get entry by ID with full relationship chain |
|
|
178
|
+
| `knowledge_update` | Update an entry with edit history tracking |
|
|
179
|
+
| `knowledge_related` | Find related entries (explicit + semantic similarity) |
|
|
180
|
+
| `knowledge_list_tags` | List all tags with counts |
|
|
181
|
+
| `knowledge_triage` | Submit to triage queue for review |
|
|
182
|
+
| `knowledge_history` | Get edit history for an entry (who changed what, when, why) |
|
|
183
|
+
| `knowledge_reindex` | Backfill missing embeddings |
|
|
184
|
+
| `knowledge_link` | Create related/sibling relationships between entries |
|
|
185
|
+
|
|
186
|
+
## Schema
|
|
187
|
+
|
|
188
|
+
Tables in the `kb` database:
|
|
189
|
+
|
|
190
|
+
- **KnowledgeBase** — KB registry (multi-tenant)
|
|
191
|
+
- **KnowledgeEntry** — core entries with HNSW vector index, `@relationship` directives for supersession/siblings/related, `@createdTime`/`@updatedTime`
|
|
192
|
+
- **KnowledgeEntryEdit** — append-only edit history audit log
|
|
193
|
+
- **TriageItem** — webhook intake queue (7-day TTL)
|
|
194
|
+
- **KnowledgeTag** — tag name as primary key with entry counts
|
|
195
|
+
- **QueryLog** — search analytics (30-day TTL)
|
|
196
|
+
- **ServiceKey** — API keys with scrypt-hashed secrets
|
|
197
|
+
- **WebhookEndpoint** — per-KB webhook secrets
|
|
198
|
+
- **WebhookDelivery** — delivery ID dedup across workers (1-hour TTL)
|
|
199
|
+
- **OAuthClient** — dynamic client registrations (RFC 7591)
|
|
200
|
+
- **OAuthCode** — authorization codes (5-minute TTL)
|
|
201
|
+
- **OAuthRefreshToken** — refresh tokens (30-day TTL)
|
|
202
|
+
- **OAuthSigningKey** — RSA key pair for JWT signing
|
|
203
|
+
|
|
204
|
+
### Applicability Scoping
|
|
205
|
+
|
|
206
|
+
Entries carry an `appliesTo` scope:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"harper": ">=4.0 <5.0",
|
|
211
|
+
"storageEngine": "lmdb",
|
|
212
|
+
"node": ">=22",
|
|
213
|
+
"platform": "linux"
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Search results are boosted or demoted (never hidden) based on the caller's context.
|
|
218
|
+
|
|
219
|
+
### Entry Relationships
|
|
220
|
+
|
|
221
|
+
- **Supersedes** — "This replaces that for newer versions"
|
|
222
|
+
- **Siblings** — "Same topic, different config" (e.g., LMDB vs RocksDB behavior)
|
|
223
|
+
- **Related** — loose "see also" association
|
|
224
|
+
|
|
225
|
+
## Auth Model
|
|
226
|
+
|
|
227
|
+
| Role | Read | Write | Review | Manage |
|
|
228
|
+
| ----------------- | ---- | ---------------------------- | ------ | ------ |
|
|
229
|
+
| `team` | Yes | Yes | Yes | Yes |
|
|
230
|
+
| `ai_agent` | Yes | Yes (flagged `ai-generated`) | No | No |
|
|
231
|
+
| `service_account` | Yes | Triage queue only | No | No |
|
|
232
|
+
|
|
233
|
+
MCP uses OAuth 2.1 with PKCE for authentication. MCP clients discover auth requirements via `/.well-known/oauth-protected-resource`, register dynamically, and authenticate through a browser-based login flow (GitHub OAuth primary, Harper credentials fallback). The web UI uses GitHub OAuth via `@harperfast/oauth` with Harper credentials as fallback.
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Build
|
|
239
|
+
npm run build
|
|
240
|
+
|
|
241
|
+
# Run tests (414 tests)
|
|
242
|
+
npm test
|
|
243
|
+
|
|
244
|
+
# Test with coverage
|
|
245
|
+
npm run test:coverage
|
|
246
|
+
|
|
247
|
+
# Watch mode
|
|
248
|
+
npm run dev
|
|
249
|
+
|
|
250
|
+
# For local semantic search, install node-llama-cpp
|
|
251
|
+
npm install node-llama-cpp
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Testing
|
|
255
|
+
|
|
256
|
+
Tests use Node.js built-in test runner (`node:test`) with mock Harper globals (in-memory tables). Tests run against compiled output in `dist/`.
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
npm test
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Fabric Deployment
|
|
263
|
+
|
|
264
|
+
For deploying to Harper Fabric, `harper-fabric-embeddings` is installed automatically as an optional dependency — no node-llama-cpp trimming or special build steps needed.
|
|
265
|
+
|
|
266
|
+
```dockerfile
|
|
267
|
+
# Dockerfile.build
|
|
268
|
+
FROM --platform=linux/amd64 node:22-slim AS build
|
|
269
|
+
WORKDIR /build
|
|
270
|
+
|
|
271
|
+
COPY package.json package-lock.json ./
|
|
272
|
+
RUN npm ci --omit=dev
|
|
273
|
+
|
|
274
|
+
# Remove harperdb (provided by Fabric runtime)
|
|
275
|
+
RUN rm -rf node_modules/harperdb node_modules/.bin/harperdb
|
|
276
|
+
|
|
277
|
+
COPY config.yaml ./
|
|
278
|
+
COPY .env ./
|
|
279
|
+
|
|
280
|
+
FROM --platform=linux/amd64 node:22-slim AS package
|
|
281
|
+
WORKDIR /out
|
|
282
|
+
COPY --from=build /build /out/app
|
|
283
|
+
RUN tar czf /out/app.tar.gz -C /out app
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT
|
package/config.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Knowledge Base Plugin Configuration
|
|
2
|
+
# This file defines the plugin entry point and default settings
|
|
3
|
+
# All settings can be overridden in your application's config.yaml
|
|
4
|
+
|
|
5
|
+
# Plugin entry point (required by Harper)
|
|
6
|
+
pluginModule: 'dist/index.js'
|
|
7
|
+
|
|
8
|
+
# GraphQL schema for knowledge base tables
|
|
9
|
+
graphqlSchema:
|
|
10
|
+
files: 'schema/*.graphql'
|
|
11
|
+
|
|
12
|
+
# Default settings (optional - these are used if not specified in app config)
|
|
13
|
+
embeddingModel: 'nomic-embed-text'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding Model Management
|
|
3
|
+
*
|
|
4
|
+
* Generates vector embeddings for semantic search. Supports two backends:
|
|
5
|
+
*
|
|
6
|
+
* 1. harper-fabric-embeddings — Minimal native wrapper (~19 MB).
|
|
7
|
+
* Preferred on Fabric. Requires a pre-staged model file.
|
|
8
|
+
*
|
|
9
|
+
* 2. node-llama-cpp — Full-featured wrapper (~250 MB+).
|
|
10
|
+
* Fallback for local dev. Downloads the model on first run.
|
|
11
|
+
*
|
|
12
|
+
* The backend is selected automatically: fabric-llama-embeddings is tried
|
|
13
|
+
* first, and node-llama-cpp is used if it's not available.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the embedding model.
|
|
17
|
+
* Tries fabric-llama-embeddings first, then falls back to node-llama-cpp.
|
|
18
|
+
*/
|
|
19
|
+
export declare function initEmbeddingModel(config: {
|
|
20
|
+
embeddingModel: string;
|
|
21
|
+
componentDir: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Generate an embedding vector for the given text.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateEmbedding(text: string): Promise<number[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Clean up embedding model resources.
|
|
29
|
+
*/
|
|
30
|
+
export declare function dispose(): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=embeddings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../../src/core/embeddings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiCH;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhH;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKvE;AAED;;GAEG;AACH,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAM7C"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding Model Management
|
|
3
|
+
*
|
|
4
|
+
* Generates vector embeddings for semantic search. Supports two backends:
|
|
5
|
+
*
|
|
6
|
+
* 1. harper-fabric-embeddings — Minimal native wrapper (~19 MB).
|
|
7
|
+
* Preferred on Fabric. Requires a pre-staged model file.
|
|
8
|
+
*
|
|
9
|
+
* 2. node-llama-cpp — Full-featured wrapper (~250 MB+).
|
|
10
|
+
* Fallback for local dev. Downloads the model on first run.
|
|
11
|
+
*
|
|
12
|
+
* The backend is selected automatically: fabric-llama-embeddings is tried
|
|
13
|
+
* first, and node-llama-cpp is used if it's not available.
|
|
14
|
+
*/
|
|
15
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
16
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
17
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
18
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return path;
|
|
22
|
+
};
|
|
23
|
+
import { writeFile, readFile, unlink, mkdir } from 'node:fs/promises';
|
|
24
|
+
import { existsSync } from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
let backend = null;
|
|
27
|
+
// ─── Model configuration ───────────────────────────────────────────────────
|
|
28
|
+
const MODEL_CONFIGS = {
|
|
29
|
+
'nomic-embed-text': {
|
|
30
|
+
repo: 'nomic-ai/nomic-embed-text-v1.5-GGUF',
|
|
31
|
+
file: 'nomic-embed-text-v1.5.Q4_K_M.gguf',
|
|
32
|
+
},
|
|
33
|
+
'nomic-embed-text-v2-moe': {
|
|
34
|
+
repo: 'nomic-ai/nomic-embed-text-v2-moe-GGUF',
|
|
35
|
+
file: 'nomic-embed-text-v2-moe.Q4_K_M.gguf',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
// Module-level models directory, set during initEmbeddingModel
|
|
39
|
+
let modelsDir = null;
|
|
40
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Initialize the embedding model.
|
|
43
|
+
* Tries fabric-llama-embeddings first, then falls back to node-llama-cpp.
|
|
44
|
+
*/
|
|
45
|
+
export async function initEmbeddingModel(config) {
|
|
46
|
+
if (backend) {
|
|
47
|
+
logger?.debug?.('Embedding model already initialized');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const modelName = config.embeddingModel || 'nomic-embed-text';
|
|
51
|
+
modelsDir = path.join(config.componentDir, 'models');
|
|
52
|
+
// Try fabric-llama-embeddings first (lightweight, Fabric-optimized)
|
|
53
|
+
try {
|
|
54
|
+
backend = await initFabricBackend(modelName);
|
|
55
|
+
logger?.info?.(`Embedding model "${modelName}" loaded via fabric-llama-embeddings`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logger?.debug?.('fabric-llama-embeddings not available, trying node-llama-cpp:', err.message);
|
|
60
|
+
}
|
|
61
|
+
// Fall back to node-llama-cpp (full-featured, handles downloads)
|
|
62
|
+
backend = await initNodeLlamaCppBackend(modelName);
|
|
63
|
+
logger?.info?.(`Embedding model "${modelName}" loaded via node-llama-cpp`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate an embedding vector for the given text.
|
|
67
|
+
*/
|
|
68
|
+
export async function generateEmbedding(text) {
|
|
69
|
+
if (!backend) {
|
|
70
|
+
throw new Error('Embedding model not initialized. Call initEmbeddingModel() first.');
|
|
71
|
+
}
|
|
72
|
+
return backend.generateEmbedding(text);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Clean up embedding model resources.
|
|
76
|
+
*/
|
|
77
|
+
export async function dispose() {
|
|
78
|
+
if (backend) {
|
|
79
|
+
await backend.dispose();
|
|
80
|
+
backend = null;
|
|
81
|
+
}
|
|
82
|
+
logger?.info?.('Embedding model disposed');
|
|
83
|
+
}
|
|
84
|
+
// ─── fabric-llama-embeddings backend ────────────────────────────────────────
|
|
85
|
+
async function initFabricBackend(modelName) {
|
|
86
|
+
const fabricPkg = 'harper-fabric-embeddings';
|
|
87
|
+
const fabricModule = (await import(__rewriteRelativeImportExtension(fabricPkg)));
|
|
88
|
+
// Pass modelsDir so the fabric backend can find or download the model
|
|
89
|
+
await fabricModule.init({ modelsDir: modelsDir, modelName });
|
|
90
|
+
return {
|
|
91
|
+
generateEmbedding: (text) => fabricModule.embed(text),
|
|
92
|
+
dispose: () => fabricModule.dispose(),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function initNodeLlamaCppBackend(modelName) {
|
|
96
|
+
const modelPath = await downloadModelIfNeeded(modelName);
|
|
97
|
+
// @ts-expect-error — node-llama-cpp is an optional manual install, not a declared dependency
|
|
98
|
+
const { getLlama } = (await import('node-llama-cpp'));
|
|
99
|
+
const llama = await getLlama({ progressLogs: false, build: 'never' });
|
|
100
|
+
const model = await llama.loadModel({ modelPath });
|
|
101
|
+
const ctx = await model.createEmbeddingContext({ contextSize: 'auto' });
|
|
102
|
+
return {
|
|
103
|
+
async generateEmbedding(text) {
|
|
104
|
+
const result = await ctx.getEmbeddingFor(text);
|
|
105
|
+
return Array.from(result.vector);
|
|
106
|
+
},
|
|
107
|
+
async dispose() {
|
|
108
|
+
if (ctx && !ctx.disposed)
|
|
109
|
+
await ctx.dispose();
|
|
110
|
+
await model.dispose();
|
|
111
|
+
await llama.dispose();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// ─── Model download (node-llama-cpp only) ───────────────────────────────────
|
|
116
|
+
function getModelUri(modelName) {
|
|
117
|
+
const config = MODEL_CONFIGS[modelName];
|
|
118
|
+
if (!config) {
|
|
119
|
+
throw new Error(`Unknown embedding model: ${modelName}. Supported: ${Object.keys(MODEL_CONFIGS).join(', ')}`);
|
|
120
|
+
}
|
|
121
|
+
return `hf:${config.repo}/${config.file}`;
|
|
122
|
+
}
|
|
123
|
+
function getLockFilePath(modelName) {
|
|
124
|
+
return path.join(modelsDir, `${modelName}.lock`);
|
|
125
|
+
}
|
|
126
|
+
async function acquireDownloadLock(modelName) {
|
|
127
|
+
const lockPath = getLockFilePath(modelName);
|
|
128
|
+
try {
|
|
129
|
+
if (existsSync(lockPath)) {
|
|
130
|
+
const lockContent = await readFile(lockPath, 'utf-8');
|
|
131
|
+
const lockTime = parseInt(lockContent, 10);
|
|
132
|
+
if (!isNaN(lockTime) && Date.now() - lockTime < 10 * 60 * 1000) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
await writeFile(lockPath, String(Date.now()), { flag: 'wx' }).catch(async () => {
|
|
137
|
+
await writeFile(lockPath, String(Date.now()));
|
|
138
|
+
});
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function releaseDownloadLock(modelName) {
|
|
146
|
+
const lockPath = getLockFilePath(modelName);
|
|
147
|
+
try {
|
|
148
|
+
await unlink(lockPath);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Already removed
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function waitForDownload(modelName, modelPath) {
|
|
155
|
+
const lockPath = getLockFilePath(modelName);
|
|
156
|
+
const maxWait = 10 * 60 * 1000;
|
|
157
|
+
const pollInterval = 2000;
|
|
158
|
+
const start = Date.now();
|
|
159
|
+
while (Date.now() - start < maxWait) {
|
|
160
|
+
if (existsSync(modelPath) && !existsSync(lockPath)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`Timed out waiting for model download: ${modelName}`);
|
|
166
|
+
}
|
|
167
|
+
async function downloadModelIfNeeded(modelName) {
|
|
168
|
+
const modelUri = getModelUri(modelName);
|
|
169
|
+
const dir = modelsDir;
|
|
170
|
+
await mkdir(dir, { recursive: true });
|
|
171
|
+
const { createModelDownloader } =
|
|
172
|
+
// @ts-expect-error — node-llama-cpp is an optional manual install, not a declared dependency
|
|
173
|
+
(await import('node-llama-cpp'));
|
|
174
|
+
const downloader = await createModelDownloader({
|
|
175
|
+
modelUri,
|
|
176
|
+
dirPath: dir,
|
|
177
|
+
skipExisting: true,
|
|
178
|
+
});
|
|
179
|
+
const modelPath = downloader.entrypointFilePath;
|
|
180
|
+
if (existsSync(modelPath)) {
|
|
181
|
+
return modelPath;
|
|
182
|
+
}
|
|
183
|
+
const acquired = await acquireDownloadLock(modelName);
|
|
184
|
+
if (!acquired) {
|
|
185
|
+
logger?.info?.(`Another thread is downloading ${modelName}, waiting...`);
|
|
186
|
+
await waitForDownload(modelName, modelPath);
|
|
187
|
+
return modelPath;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
logger?.info?.(`Downloading embedding model: ${modelName} from ${modelUri}`);
|
|
191
|
+
const resultPath = await downloader.download();
|
|
192
|
+
logger?.info?.(`Model ${modelName} downloaded successfully to ${resultPath}`);
|
|
193
|
+
return resultPath;
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await releaseDownloadLock(modelName);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=embeddings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../src/core/embeddings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B,IAAI,OAAO,GAA4B,IAAI,CAAC;AAE5C,8EAA8E;AAE9E,MAAM,aAAa,GAAmD;IACrE,kBAAkB,EAAE;QACnB,IAAI,EAAE,qCAAqC;QAC3C,IAAI,EAAE,mCAAmC;KACzC;IACD,yBAAyB,EAAE;QAC1B,IAAI,EAAE,uCAAuC;QAC7C,IAAI,EAAE,qCAAqC;KAC3C;CACD,CAAC;AAEF,+DAA+D;AAC/D,IAAI,SAAS,GAAkB,IAAI,CAAC;AAEpC,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAwD;IAChG,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,EAAE,KAAK,EAAE,CAAC,qCAAqC,CAAC,CAAC;QACvD,OAAO;IACR,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,IAAI,kBAAkB,CAAC;IAC9D,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAErD,oEAAoE;IACpE,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,EAAE,IAAI,EAAE,CAAC,oBAAoB,SAAS,sCAAsC,CAAC,CAAC;QACpF,OAAO;IACR,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,EAAE,KAAK,EAAE,CAAC,+DAA+D,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;IAC1G,CAAC;IAED,iEAAiE;IACjE,OAAO,GAAG,MAAM,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,EAAE,IAAI,EAAE,CAAC,oBAAoB,SAAS,6BAA6B,CAAC,CAAC;AAC5E,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IACnD,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO;IAC5B,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO,GAAG,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,CAAC,0BAA0B,CAAC,CAAC;AAC5C,CAAC;AAED,+EAA+E;AAE/E,KAAK,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,SAAS,GAAG,0BAA0B,CAAC;IAC7C,MAAM,YAAY,GAAG,CAAC,MAAM,MAAM,kCAAC,SAAS,EAAC,CAY5C,CAAC;IAEF,sEAAsE;IACtE,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IAE9D,OAAO;QACN,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC;QACrD,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE;KACrC,CAAC;AACH,CAAC;AAsBD,KAAK,UAAU,uBAAuB,CAAC,SAAiB;IACvD,MAAM,SAAS,GAAG,MAAM,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAEzD,6FAA6F;IAC7F,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAEnD,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,sBAAsB,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IAExE,OAAO;QACN,KAAK,CAAC,iBAAiB,CAAC,IAAY;YACnC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAC/C,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,OAAO;YACZ,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ;gBAAE,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;YAC9C,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACD,CAAC;AACH,CAAC;AAED,+EAA+E;AAE/E,SAAS,WAAW,CAAC,SAAiB;IACrC,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,SAAS,gBAAgB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/G,CAAC;IACD,OAAO,MAAM,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAU,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IACnD,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC;QACJ,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACtD,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;gBAChE,OAAO,KAAK,CAAC;YACd,CAAC;QACF,CAAC;QACD,MAAM,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;YAC9E,MAAM,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IACnD,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACR,kBAAkB;IACnB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,SAAiB,EAAE,SAAiB;IAClE,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC/B,MAAM,YAAY,GAAG,IAAI,CAAC;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpD,OAAO;QACR,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,yCAAyC,SAAS,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,SAAiB;IACrD,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,SAAU,CAAC;IAEvB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtC,MAAM,EAAE,qBAAqB,EAAE;IAC9B,6FAA6F;IAC7F,CAAC,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAK9B,CAAC;IAEH,MAAM,UAAU,GAAG,MAAM,qBAAqB,CAAC;QAC9C,QAAQ;QACR,OAAO,EAAE,GAAG;QACZ,YAAY,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,UAAU,CAAC,kBAAkB,CAAC;IAEhD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAEtD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,EAAE,IAAI,EAAE,CAAC,iCAAiC,SAAS,cAAc,CAAC,CAAC;QACzE,MAAM,eAAe,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC5C,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,EAAE,IAAI,EAAE,CAAC,gCAAgC,SAAS,SAAS,QAAQ,EAAE,CAAC,CAAC;QAC7E,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC;QAC/C,MAAM,EAAE,IAAI,EAAE,CAAC,SAAS,SAAS,+BAA+B,UAAU,EAAE,CAAC,CAAC;QAC9E,OAAO,UAAU,CAAC;IACnB,CAAC;YAAS,CAAC;QACV,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Entry Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for knowledge base entries. Handles embedding generation,
|
|
5
|
+
* tag synchronization, and relationship management.
|
|
6
|
+
*/
|
|
7
|
+
import type { KnowledgeEntry, KnowledgeEntryInput, KnowledgeEntryUpdate } from '../types.ts';
|
|
8
|
+
/**
|
|
9
|
+
* Strip embedding vectors from an entry to keep responses compact.
|
|
10
|
+
* Embeddings are large float arrays not useful in API responses.
|
|
11
|
+
*
|
|
12
|
+
* NOTE: Harper database records use non-enumerable properties, so
|
|
13
|
+
* object spread ({...record}) produces an empty object. We must
|
|
14
|
+
* explicitly read each field.
|
|
15
|
+
*/
|
|
16
|
+
export declare function stripEmbedding(entry: any): Omit<KnowledgeEntry, 'embedding'>;
|
|
17
|
+
/**
|
|
18
|
+
* Create a new knowledge entry.
|
|
19
|
+
*
|
|
20
|
+
* Generates an embedding from title + content, synchronizes tags,
|
|
21
|
+
* and stores the entry. A UUID is generated if no id is provided.
|
|
22
|
+
*
|
|
23
|
+
* @param data - Entry data to create
|
|
24
|
+
* @returns The created knowledge entry
|
|
25
|
+
*/
|
|
26
|
+
export declare function createEntry(data: KnowledgeEntryInput): Promise<KnowledgeEntry>;
|
|
27
|
+
/**
|
|
28
|
+
* Get a knowledge entry by ID.
|
|
29
|
+
*
|
|
30
|
+
* @param id - Entry ID
|
|
31
|
+
* @returns The entry, or null if not found
|
|
32
|
+
*/
|
|
33
|
+
export declare function getEntry(id: string): Promise<KnowledgeEntry | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Update an existing knowledge entry.
|
|
36
|
+
*
|
|
37
|
+
* Merges the update data with the existing entry. If title or content changed,
|
|
38
|
+
* regenerates the embedding. Synchronizes tag counts if tags changed.
|
|
39
|
+
* Optionally logs the edit to the history table.
|
|
40
|
+
*
|
|
41
|
+
* @param id - ID of the entry to update
|
|
42
|
+
* @param data - Fields to update
|
|
43
|
+
* @param options - Optional edit tracking metadata
|
|
44
|
+
* @returns The updated entry
|
|
45
|
+
* @throws Error if the entry does not exist
|
|
46
|
+
*/
|
|
47
|
+
export declare function updateEntry(id: string, data: KnowledgeEntryUpdate, options?: {
|
|
48
|
+
editedBy?: string;
|
|
49
|
+
editSummary?: string;
|
|
50
|
+
}): Promise<KnowledgeEntry>;
|
|
51
|
+
/**
|
|
52
|
+
* Mark an entry as deprecated.
|
|
53
|
+
*
|
|
54
|
+
* @param id - ID of the entry to deprecate
|
|
55
|
+
* @throws Error if the entry does not exist
|
|
56
|
+
*/
|
|
57
|
+
export declare function deprecateEntry(id: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Link a new entry as superseding an old entry.
|
|
60
|
+
*
|
|
61
|
+
* Sets newEntry.supersedesId = oldId and oldEntry.supersededById = newId.
|
|
62
|
+
*
|
|
63
|
+
* @param newId - ID of the new (superseding) entry
|
|
64
|
+
* @param oldId - ID of the old (superseded) entry
|
|
65
|
+
* @throws Error if either entry does not exist
|
|
66
|
+
*/
|
|
67
|
+
export declare function linkSupersedes(newId: string, oldId: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Link multiple entries as siblings.
|
|
70
|
+
*
|
|
71
|
+
* For each entry, adds all other entry IDs to its siblingIds array (deduplicated).
|
|
72
|
+
*
|
|
73
|
+
* @param ids - IDs of entries to link as siblings
|
|
74
|
+
* @throws Error if any entry does not exist
|
|
75
|
+
*/
|
|
76
|
+
export declare function linkSiblings(ids: string[]): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Reindex embeddings for all entries missing them.
|
|
79
|
+
*
|
|
80
|
+
* Iterates every KnowledgeEntry, generates embeddings for any that
|
|
81
|
+
* don't have one yet, and writes them back. Returns counts of
|
|
82
|
+
* processed, updated, and failed entries.
|
|
83
|
+
*/
|
|
84
|
+
export declare function reindexEmbeddings(kbId: string): Promise<{
|
|
85
|
+
total: number;
|
|
86
|
+
updated: number;
|
|
87
|
+
failed: number;
|
|
88
|
+
skipped: number;
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Link two entries as related.
|
|
92
|
+
*
|
|
93
|
+
* Adds relatedId to the entry's relatedIds array (deduplicated).
|
|
94
|
+
* This is a one-directional link; call twice for bidirectional.
|
|
95
|
+
*
|
|
96
|
+
* @param id - ID of the entry to add a related link to
|
|
97
|
+
* @param relatedId - ID of the related entry
|
|
98
|
+
* @throws Error if the entry does not exist
|
|
99
|
+
*/
|
|
100
|
+
export declare function linkRelated(id: string, relatedId: string): Promise<void>;
|
|
101
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/core/entries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7F;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAyB5E;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAuCpF;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAGzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAChC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,oBAAoB,EAC1B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,OAAO,CAAC,cAAc,CAAC,CA+CzB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW9D;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBhF;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC/D;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC,CAmCD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9E"}
|