harper-knowledge 0.1.0 → 0.1.1
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/dist/core/embeddings.d.ts +3 -2
- package/dist/core/embeddings.js +9 -13
- package/dist/index.js +3 -1
- package/dist/resources/MeResource.d.ts +30 -0
- package/dist/resources/MeResource.js +39 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/web/js/app.js +5 -5
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Initialize the embedding model.
|
|
10
|
-
* Downloads the model to
|
|
10
|
+
* Downloads the model to the given directory if not present.
|
|
11
11
|
* Uses file-based locking so only one thread downloads.
|
|
12
12
|
*
|
|
13
|
-
* @param config - Plugin configuration with embeddingModel name
|
|
13
|
+
* @param config - Plugin configuration with embeddingModel name and componentDir
|
|
14
14
|
*/
|
|
15
15
|
export declare function initEmbeddingModel(config: {
|
|
16
16
|
embeddingModel: string;
|
|
17
|
+
componentDir: string;
|
|
17
18
|
}): Promise<void>;
|
|
18
19
|
/**
|
|
19
20
|
* Generate an embedding vector for the given text.
|
package/dist/core/embeddings.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import { writeFile, readFile, unlink, mkdir } from "node:fs/promises";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import os from "node:os";
|
|
12
11
|
// Module-level state for the loaded model and context
|
|
13
12
|
let llama = null;
|
|
14
13
|
let embeddingModel = null;
|
|
@@ -24,17 +23,13 @@ const MODEL_CONFIGS = {
|
|
|
24
23
|
file: "nomic-embed-text-v2-moe.Q4_K_M.gguf",
|
|
25
24
|
},
|
|
26
25
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*/
|
|
30
|
-
function getModelsDir() {
|
|
31
|
-
return path.join(os.homedir(), "hdb", "models");
|
|
32
|
-
}
|
|
26
|
+
// Module-level models directory, set during initEmbeddingModel
|
|
27
|
+
let modelsDir = null;
|
|
33
28
|
/**
|
|
34
29
|
* Get the lock file path for download synchronization.
|
|
35
30
|
*/
|
|
36
31
|
function getLockFilePath(modelName) {
|
|
37
|
-
return path.join(
|
|
32
|
+
return path.join(modelsDir, `${modelName}.lock`);
|
|
38
33
|
}
|
|
39
34
|
/**
|
|
40
35
|
* Get the model URI for node-llama-cpp downloads.
|
|
@@ -107,16 +102,16 @@ async function waitForDownload(modelName, modelPath) {
|
|
|
107
102
|
*/
|
|
108
103
|
async function downloadModelIfNeeded(modelName) {
|
|
109
104
|
const modelUri = getModelUri(modelName);
|
|
110
|
-
const
|
|
105
|
+
const dir = modelsDir;
|
|
111
106
|
// Ensure models directory exists
|
|
112
|
-
await mkdir(
|
|
107
|
+
await mkdir(dir, { recursive: true });
|
|
113
108
|
// Use node-llama-cpp to resolve the actual file path and download if needed.
|
|
114
109
|
// node-llama-cpp prefixes filenames (e.g., hf_nomic-ai_<file>.gguf),
|
|
115
110
|
// so we use its entrypointFilePath rather than guessing the name.
|
|
116
111
|
const { createModelDownloader } = (await import("node-llama-cpp"));
|
|
117
112
|
const downloader = await createModelDownloader({
|
|
118
113
|
modelUri,
|
|
119
|
-
dirPath:
|
|
114
|
+
dirPath: dir,
|
|
120
115
|
skipExisting: true,
|
|
121
116
|
});
|
|
122
117
|
const modelPath = downloader.entrypointFilePath;
|
|
@@ -144,13 +139,14 @@ async function downloadModelIfNeeded(modelName) {
|
|
|
144
139
|
}
|
|
145
140
|
/**
|
|
146
141
|
* Initialize the embedding model.
|
|
147
|
-
* Downloads the model to
|
|
142
|
+
* Downloads the model to the given directory if not present.
|
|
148
143
|
* Uses file-based locking so only one thread downloads.
|
|
149
144
|
*
|
|
150
|
-
* @param config - Plugin configuration with embeddingModel name
|
|
145
|
+
* @param config - Plugin configuration with embeddingModel name and componentDir
|
|
151
146
|
*/
|
|
152
147
|
export async function initEmbeddingModel(config) {
|
|
153
148
|
const modelName = config.embeddingModel || "nomic-embed-text";
|
|
149
|
+
modelsDir = path.join(config.componentDir, "models");
|
|
154
150
|
if (embeddingModel) {
|
|
155
151
|
logger?.debug?.("Embedding model already initialized");
|
|
156
152
|
return;
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { TagResource } from "./resources/TagResource.js";
|
|
|
14
14
|
import { QueryLogResource } from "./resources/QueryLogResource.js";
|
|
15
15
|
import { ServiceKeyResource } from "./resources/ServiceKeyResource.js";
|
|
16
16
|
import { HistoryResource } from "./resources/HistoryResource.js";
|
|
17
|
+
import { MeResource } from "./resources/MeResource.js";
|
|
17
18
|
import { createMcpMiddleware } from "./mcp/server.js";
|
|
18
19
|
import { createWebhookMiddleware } from "./webhooks/middleware.js";
|
|
19
20
|
import { createOAuthMiddleware } from "./oauth/middleware.js";
|
|
@@ -38,7 +39,7 @@ export async function handleApplication(scope) {
|
|
|
38
39
|
// Don't await — the download can take minutes and would exceed Harper's
|
|
39
40
|
// 30-second handleApplication timeout. Semantic search degrades gracefully
|
|
40
41
|
// to keyword-only mode until the model is ready.
|
|
41
|
-
initEmbeddingModel({ embeddingModel }).then(() => scopeLogger?.info?.(`Embedding model "${embeddingModel}" loaded`), (error) => scopeLogger?.error?.("Failed to initialize embedding model — semantic search will be unavailable:", error.message));
|
|
42
|
+
initEmbeddingModel({ embeddingModel, componentDir: scope.directory }).then(() => scopeLogger?.info?.(`Embedding model "${embeddingModel}" loaded`), (error) => scopeLogger?.error?.("Failed to initialize embedding model — semantic search will be unavailable:", error.message));
|
|
42
43
|
// Initialize OAuth signing keys (generates RSA key pair on first run)
|
|
43
44
|
try {
|
|
44
45
|
await ensureSigningKey();
|
|
@@ -53,6 +54,7 @@ export async function handleApplication(scope) {
|
|
|
53
54
|
scope.resources.set("QueryLog", QueryLogResource);
|
|
54
55
|
scope.resources.set("ServiceKey", ServiceKeyResource);
|
|
55
56
|
scope.resources.set("History", HistoryResource);
|
|
57
|
+
scope.resources.set("me", MeResource);
|
|
56
58
|
// Register OAuth endpoints (must be first — handles /.well-known/*, /oauth/*)
|
|
57
59
|
scope.server.http?.(createOAuthMiddleware());
|
|
58
60
|
// Register webhook intake middleware (before MCP so /webhooks/* is handled first)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Me Resource
|
|
3
|
+
*
|
|
4
|
+
* Public endpoint that returns the current user's session info.
|
|
5
|
+
* Reads from the Harper session cookie — no extra state tracked.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* GET /me — returns current authenticated user or { authenticated: false }
|
|
9
|
+
*/
|
|
10
|
+
declare const MeResource_base: any;
|
|
11
|
+
export declare class MeResource extends MeResource_base {
|
|
12
|
+
static loadAsInstance: boolean;
|
|
13
|
+
get(): {
|
|
14
|
+
authenticated: boolean;
|
|
15
|
+
username: any;
|
|
16
|
+
name: any;
|
|
17
|
+
provider: any;
|
|
18
|
+
} | {
|
|
19
|
+
authenticated: boolean;
|
|
20
|
+
username: any;
|
|
21
|
+
provider: string;
|
|
22
|
+
name?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
authenticated: boolean;
|
|
25
|
+
username?: undefined;
|
|
26
|
+
name?: undefined;
|
|
27
|
+
provider?: undefined;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Me Resource
|
|
3
|
+
*
|
|
4
|
+
* Public endpoint that returns the current user's session info.
|
|
5
|
+
* Reads from the Harper session cookie — no extra state tracked.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* GET /me — returns current authenticated user or { authenticated: false }
|
|
9
|
+
*/
|
|
10
|
+
function getResourceClass() {
|
|
11
|
+
return globalThis.Resource;
|
|
12
|
+
}
|
|
13
|
+
export class MeResource extends getResourceClass() {
|
|
14
|
+
static loadAsInstance = false;
|
|
15
|
+
get() {
|
|
16
|
+
const context = this.getContext();
|
|
17
|
+
// Check for OAuth session (via @harperfast/oauth)
|
|
18
|
+
const oauthUser = context?.session?.oauthUser;
|
|
19
|
+
if (oauthUser) {
|
|
20
|
+
return {
|
|
21
|
+
authenticated: true,
|
|
22
|
+
username: oauthUser.username,
|
|
23
|
+
name: oauthUser.name,
|
|
24
|
+
provider: oauthUser.provider,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Check for Harper user (Basic auth or session-based)
|
|
28
|
+
const user = context?.user;
|
|
29
|
+
if (user) {
|
|
30
|
+
const username = typeof user === "string" ? user : user.username || user.id;
|
|
31
|
+
return {
|
|
32
|
+
authenticated: true,
|
|
33
|
+
username,
|
|
34
|
+
provider: "harper",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { authenticated: false };
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/web/js/app.js
CHANGED
|
@@ -104,20 +104,20 @@ const auth = {
|
|
|
104
104
|
authenticated: false,
|
|
105
105
|
|
|
106
106
|
async check() {
|
|
107
|
-
// Check
|
|
107
|
+
// Check session via /me endpoint (reads Harper session cookie)
|
|
108
108
|
try {
|
|
109
|
-
const res = await fetch("/
|
|
109
|
+
const res = await fetch("/me");
|
|
110
110
|
if (res.ok) {
|
|
111
111
|
const data = await res.json();
|
|
112
|
-
if (data
|
|
113
|
-
this.user = data.username
|
|
112
|
+
if (data?.authenticated) {
|
|
113
|
+
this.user = data.username;
|
|
114
114
|
this.authenticated = true;
|
|
115
115
|
updateAuthUI();
|
|
116
116
|
return true;
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
} catch {
|
|
120
|
-
//
|
|
120
|
+
// Endpoint unavailable
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// Check for stored Harper credentials (Basic auth)
|