harper-knowledge 0.1.0 → 0.1.2
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/oauth/authorize.d.ts +3 -3
- package/dist/oauth/authorize.js +5 -5
- package/dist/oauth/metadata.js +4 -4
- package/dist/oauth/middleware.d.ts +6 -6
- package/dist/oauth/middleware.js +13 -12
- 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)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OAuth Authorization Endpoint
|
|
3
3
|
*
|
|
4
|
-
* GET /
|
|
4
|
+
* GET /mcp-auth/authorize — MCP OAuth 2.1 authorization endpoint.
|
|
5
5
|
*
|
|
6
6
|
* Shows a login page with GitHub as the primary auth method and a
|
|
7
7
|
* subtle link to fall back to Harper credentials. If the user has an
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { HarperRequest } from "../types.ts";
|
|
15
15
|
/**
|
|
16
|
-
* Handle GET /
|
|
16
|
+
* Handle GET /mcp-auth/authorize
|
|
17
17
|
*
|
|
18
18
|
* Three modes:
|
|
19
19
|
* 1. Returning from GitHub login (`pending` param) — complete authorization.
|
|
@@ -22,6 +22,6 @@ import type { HarperRequest } from "../types.ts";
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function handleAuthorizeGet(request: HarperRequest): Promise<Response>;
|
|
24
24
|
/**
|
|
25
|
-
* Handle POST /
|
|
25
|
+
* Handle POST /mcp-auth/authorize — Harper credential login.
|
|
26
26
|
*/
|
|
27
27
|
export declare function handleAuthorizePost(request: HarperRequest): Promise<Response>;
|
package/dist/oauth/authorize.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OAuth Authorization Endpoint
|
|
3
3
|
*
|
|
4
|
-
* GET /
|
|
4
|
+
* GET /mcp-auth/authorize — MCP OAuth 2.1 authorization endpoint.
|
|
5
5
|
*
|
|
6
6
|
* Shows a login page with GitHub as the primary auth method and a
|
|
7
7
|
* subtle link to fall back to Harper credentials. If the user has an
|
|
@@ -15,7 +15,7 @@ import crypto from "node:crypto";
|
|
|
15
15
|
import { readBody, parseFormBody } from "../http-utils.js";
|
|
16
16
|
import { checkOrgMembership } from "./github.js";
|
|
17
17
|
/**
|
|
18
|
-
* Handle GET /
|
|
18
|
+
* Handle GET /mcp-auth/authorize
|
|
19
19
|
*
|
|
20
20
|
* Three modes:
|
|
21
21
|
* 1. Returning from GitHub login (`pending` param) — complete authorization.
|
|
@@ -44,7 +44,7 @@ export async function handleAuthorizeGet(request) {
|
|
|
44
44
|
return loginPage(params);
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Handle POST /
|
|
47
|
+
* Handle POST /mcp-auth/authorize — Harper credential login.
|
|
48
48
|
*/
|
|
49
49
|
export async function handleAuthorizePost(request) {
|
|
50
50
|
let form;
|
|
@@ -266,7 +266,7 @@ async function buildGitHubLoginUrl(params) {
|
|
|
266
266
|
redirectUri: params.redirect_uri,
|
|
267
267
|
type: "pending",
|
|
268
268
|
});
|
|
269
|
-
const returnPath = `/
|
|
269
|
+
const returnPath = `/mcp-auth/authorize?pending=${pendingId}`;
|
|
270
270
|
return `/oauth/github/login?redirect=${encodeURIComponent(returnPath)}`;
|
|
271
271
|
}
|
|
272
272
|
/**
|
|
@@ -361,7 +361,7 @@ async function loginPage(params, errorMsg) {
|
|
|
361
361
|
<button type="button" class="cred-toggle" onclick="document.querySelector('.cred-form').classList.toggle('visible');this.style.display='none'">
|
|
362
362
|
Sign in with Harper credentials
|
|
363
363
|
</button>
|
|
364
|
-
<form method="POST" action="/
|
|
364
|
+
<form method="POST" action="/mcp-auth/authorize" class="cred-form${errorMsg ? " visible" : ""}">
|
|
365
365
|
<input type="hidden" name="client_id" value="${escapeAttr(params.client_id)}">
|
|
366
366
|
<input type="hidden" name="redirect_uri" value="${escapeAttr(params.redirect_uri)}">
|
|
367
367
|
<input type="hidden" name="response_type" value="${escapeAttr(params.response_type)}">
|
package/dist/oauth/metadata.js
CHANGED
|
@@ -32,10 +32,10 @@ export function handleAuthServerMetadata(request) {
|
|
|
32
32
|
const baseUrl = getBaseUrl(request);
|
|
33
33
|
return jsonResponse(200, {
|
|
34
34
|
issuer: baseUrl,
|
|
35
|
-
authorization_endpoint: `${baseUrl}/
|
|
36
|
-
token_endpoint: `${baseUrl}/
|
|
37
|
-
registration_endpoint: `${baseUrl}/
|
|
38
|
-
jwks_uri: `${baseUrl}/
|
|
35
|
+
authorization_endpoint: `${baseUrl}/mcp-auth/authorize`,
|
|
36
|
+
token_endpoint: `${baseUrl}/mcp-auth/token`,
|
|
37
|
+
registration_endpoint: `${baseUrl}/mcp-auth/register`,
|
|
38
|
+
jwks_uri: `${baseUrl}/mcp-auth/jwks`,
|
|
39
39
|
scopes_supported: SCOPES,
|
|
40
40
|
response_types_supported: ["code"],
|
|
41
41
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Route dispatcher for all OAuth endpoints. Registered via
|
|
5
5
|
* scope.server.http() before the MCP and webhook middlewares.
|
|
6
6
|
*
|
|
7
|
-
* Routes:
|
|
7
|
+
* Routes (MCP OAuth 2.1 — separate from @harperfast/oauth's /oauth/* Resource):
|
|
8
8
|
* GET /.well-known/oauth-protected-resource → metadata
|
|
9
9
|
* GET /.well-known/oauth-authorization-server → metadata
|
|
10
|
-
* POST /
|
|
11
|
-
* GET /
|
|
12
|
-
* POST /
|
|
13
|
-
* POST /
|
|
14
|
-
* GET /
|
|
10
|
+
* POST /mcp-auth/register → DCR
|
|
11
|
+
* GET /mcp-auth/authorize → login page
|
|
12
|
+
* POST /mcp-auth/authorize → credential validation + redirect
|
|
13
|
+
* POST /mcp-auth/token → code exchange / refresh
|
|
14
|
+
* GET /mcp-auth/jwks → public key set
|
|
15
15
|
*/
|
|
16
16
|
import type { HarperRequest } from "../types.ts";
|
|
17
17
|
type MiddlewareFn = (request: HarperRequest, next: (req: HarperRequest) => Promise<unknown>) => Promise<unknown>;
|
package/dist/oauth/middleware.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Route dispatcher for all OAuth endpoints. Registered via
|
|
5
5
|
* scope.server.http() before the MCP and webhook middlewares.
|
|
6
6
|
*
|
|
7
|
-
* Routes:
|
|
7
|
+
* Routes (MCP OAuth 2.1 — separate from @harperfast/oauth's /oauth/* Resource):
|
|
8
8
|
* GET /.well-known/oauth-protected-resource → metadata
|
|
9
9
|
* GET /.well-known/oauth-authorization-server → metadata
|
|
10
|
-
* POST /
|
|
11
|
-
* GET /
|
|
12
|
-
* POST /
|
|
13
|
-
* POST /
|
|
14
|
-
* GET /
|
|
10
|
+
* POST /mcp-auth/register → DCR
|
|
11
|
+
* GET /mcp-auth/authorize → login page
|
|
12
|
+
* POST /mcp-auth/authorize → credential validation + redirect
|
|
13
|
+
* POST /mcp-auth/token → code exchange / refresh
|
|
14
|
+
* GET /mcp-auth/jwks → public key set
|
|
15
15
|
*/
|
|
16
16
|
import { handleProtectedResourceMetadata, handleAuthServerMetadata, } from "./metadata.js";
|
|
17
17
|
import { handleRegister } from "./register.js";
|
|
@@ -34,11 +34,12 @@ export function createOAuthMiddleware() {
|
|
|
34
34
|
method === "GET") {
|
|
35
35
|
return handleAuthServerMetadata(request);
|
|
36
36
|
}
|
|
37
|
-
// OAuth endpoints
|
|
38
|
-
|
|
37
|
+
// MCP OAuth endpoints (under /mcp-auth/ to avoid conflict with
|
|
38
|
+
// @harperfast/oauth's OAuthResource which owns the /oauth/* path)
|
|
39
|
+
if (pathname === "/mcp-auth/register" && method === "POST") {
|
|
39
40
|
return handleRegister(request);
|
|
40
41
|
}
|
|
41
|
-
if (pathname === "/
|
|
42
|
+
if (pathname === "/mcp-auth/authorize") {
|
|
42
43
|
if (method === "GET") {
|
|
43
44
|
return handleAuthorizeGet(request);
|
|
44
45
|
}
|
|
@@ -46,10 +47,10 @@ export function createOAuthMiddleware() {
|
|
|
46
47
|
return handleAuthorizePost(request);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
if (pathname === "/
|
|
50
|
+
if (pathname === "/mcp-auth/token" && method === "POST") {
|
|
50
51
|
return handleToken(request);
|
|
51
52
|
}
|
|
52
|
-
if (pathname === "/
|
|
53
|
+
if (pathname === "/mcp-auth/jwks" && method === "GET") {
|
|
53
54
|
return new Response(JSON.stringify(await getJwks()), {
|
|
54
55
|
status: 200,
|
|
55
56
|
headers: {
|
|
@@ -58,7 +59,7 @@ export function createOAuthMiddleware() {
|
|
|
58
59
|
},
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
|
-
// Not
|
|
62
|
+
// Not a handled route — pass through
|
|
62
63
|
return next(request);
|
|
63
64
|
};
|
|
64
65
|
}
|
|
@@ -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)
|