@hiai-gg/docsmint 0.6.6 → 0.6.8
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/README.md +20 -1
- package/dist/backend/index.js +46 -25
- package/package.json +1 -1
- package/packages/cli/src/index.ts +1 -1
- package/packages/mcp-server/src/server.ts +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -40,7 +40,26 @@ server.
|
|
|
40
40
|
- **Own the full stack**: application data, vectors, graph, queue, and files run
|
|
41
41
|
on infrastructure you control.
|
|
42
42
|
|
|
43
|
-
## What's new in DocsMint 0.6.
|
|
43
|
+
## What's new in DocsMint 0.6.8?
|
|
44
|
+
|
|
45
|
+
- **Workspace-safe recovery.** Explicit operator reindexing carries the
|
|
46
|
+
document's workspace into every durable pipeline stage, including RLS reads.
|
|
47
|
+
- **Consistent operator authentication.** Both documented admin key headers
|
|
48
|
+
bypass interactive rate limits during maintenance operations.
|
|
49
|
+
|
|
50
|
+
DocsMint 0.6.8 includes the self-host reliability fixes introduced in 0.6.7:
|
|
51
|
+
|
|
52
|
+
- **Reliable HTTPS sessions.** Every protected SvelteKit route accepts Better
|
|
53
|
+
Auth's secure production cookie as well as the local-development cookie.
|
|
54
|
+
- **Cleaner retrieval.** Deleted documents are excluded before exact, lexical,
|
|
55
|
+
fuzzy, vector, chunk, and graph ranking.
|
|
56
|
+
- **Explicit GraphRAG providers.** Entity extraction requires its own
|
|
57
|
+
chat-completion URL and never sends chat requests to an embedding endpoint.
|
|
58
|
+
- **Safer self-host defaults.** Auth origins are validated configuration, the
|
|
59
|
+
SeaweedFS Filer UI binds to loopback, folder moves refresh knowledge
|
|
60
|
+
metadata, and backups exclude `.env`.
|
|
61
|
+
|
|
62
|
+
DocsMint 0.6.7 includes the safe index recovery introduced in 0.6.6:
|
|
44
63
|
|
|
45
64
|
- **Safe explicit index recovery.** Admin document reindexing keeps the active
|
|
46
65
|
generation searchable until a new generation has passed embedding, graph,
|
package/dist/backend/index.js
CHANGED
|
@@ -77709,6 +77709,7 @@ var init_config_schema = __esm(() => {
|
|
|
77709
77709
|
WEBHOOK_SECRET: exports_external2.string().min(1, "WEBHOOK_SECRET must not be empty").default("change-me-to-random-32-chars").refine((val) => val !== "change-me-to-random-32-chars", "WEBHOOK_SECRET must be set in production"),
|
|
77710
77710
|
BETTER_AUTH_URL: exports_external2.string().default("http://localhost:50700"),
|
|
77711
77711
|
CORS_ORIGINS: exports_external2.string().optional(),
|
|
77712
|
+
TRUSTED_ORIGINS: exports_external2.string().optional(),
|
|
77712
77713
|
WEB_PORT: exports_external2.coerce.number().default(50701),
|
|
77713
77714
|
EMBEDDING_BASE_URL: exports_external2.string().optional(),
|
|
77714
77715
|
EMBEDDING_API_KEY: exports_external2.string().optional(),
|
|
@@ -192960,7 +192961,7 @@ var auth = betterAuth({
|
|
|
192960
192961
|
}),
|
|
192961
192962
|
secret: config3.BETTER_AUTH_SECRET,
|
|
192962
192963
|
baseURL: config3.BETTER_AUTH_URL,
|
|
192963
|
-
trustedOrigins:
|
|
192964
|
+
trustedOrigins: config3.TRUSTED_ORIGINS ? config3.TRUSTED_ORIGINS.split(",").map((origin) => origin.trim()).filter(Boolean) : ["http://localhost:50701", "http://127.0.0.1:50701"],
|
|
192964
192965
|
emailAndPassword: {
|
|
192965
192966
|
enabled: true
|
|
192966
192967
|
},
|
|
@@ -193195,11 +193196,14 @@ function createRateLimiter(config4, redisClient) {
|
|
|
193195
193196
|
}
|
|
193196
193197
|
|
|
193197
193198
|
// ../../backend/src/api/middleware/rate-limit.ts
|
|
193198
|
-
function isInternalRequest(request) {
|
|
193199
|
+
function isInternalRequest(request, expected = config3.HIAI_DOCS_API_KEY) {
|
|
193199
193200
|
if (!request)
|
|
193200
193201
|
return false;
|
|
193201
|
-
|
|
193202
|
-
|
|
193202
|
+
if (!expected)
|
|
193203
|
+
return false;
|
|
193204
|
+
const headerKey = request.headers.get("x-api-key")?.trim();
|
|
193205
|
+
const bearer = /^Bearer\s+(.+)$/i.exec(request.headers.get("authorization")?.trim() ?? "")?.[1]?.trim();
|
|
193206
|
+
return headerKey === expected || bearer === expected;
|
|
193203
193207
|
}
|
|
193204
193208
|
function _limiterWithBypass(config4) {
|
|
193205
193209
|
const base = createRateLimiter(config4);
|
|
@@ -194512,6 +194516,25 @@ async function loadAdminFolderDocumentIds(folderId, limit) {
|
|
|
194512
194516
|
return limit > 0 ? query.limit(limit) : query;
|
|
194513
194517
|
});
|
|
194514
194518
|
}
|
|
194519
|
+
async function loadAdminDocumentTarget(documentId) {
|
|
194520
|
+
const rows = await withTenant(REEMBED_ADMIN_TENANT, (tx) => tx.select({ id: documents.id, workspaceId: documents.workspaceId }).from(documents).where(eq(documents.id, documentId)).limit(1));
|
|
194521
|
+
const row = rows[0];
|
|
194522
|
+
return row ? { id: row.id, workspaceId: row.workspaceId ?? undefined } : undefined;
|
|
194523
|
+
}
|
|
194524
|
+
async function reembedDocumentAdminWith(documentId, loadTarget) {
|
|
194525
|
+
const target2 = await loadTarget(documentId);
|
|
194526
|
+
if (!target2)
|
|
194527
|
+
return { found: false, enqueued: 0 };
|
|
194528
|
+
const enqueued = await enqueueReembed([target2.id], target2.workspaceId, {
|
|
194529
|
+
bypassDedup: true,
|
|
194530
|
+
forceNewGeneration: true,
|
|
194531
|
+
source: "reindex"
|
|
194532
|
+
});
|
|
194533
|
+
return { found: true, enqueued };
|
|
194534
|
+
}
|
|
194535
|
+
function reembedDocumentAdmin(documentId) {
|
|
194536
|
+
return reembedDocumentAdminWith(documentId, loadAdminDocumentTarget);
|
|
194537
|
+
}
|
|
194515
194538
|
async function reembedDocsInFolderAdminWith(folderId, loadRows) {
|
|
194516
194539
|
const limit = config3.FOLDER_REEMBED_BATCH_SIZE;
|
|
194517
194540
|
const rows = await loadRows(folderId, limit);
|
|
@@ -194613,27 +194636,19 @@ var adminRoutes = new Elysia({ prefix: "/api/admin" }).post("/reindex/:docId", a
|
|
|
194613
194636
|
return { error: "Invalid or missing admin API key" };
|
|
194614
194637
|
}
|
|
194615
194638
|
try {
|
|
194616
|
-
const
|
|
194617
|
-
|
|
194618
|
-
return rows.length > 0;
|
|
194619
|
-
});
|
|
194620
|
-
if (!existing) {
|
|
194639
|
+
const result = await reembedDocumentAdmin(params.docId);
|
|
194640
|
+
if (!result.found) {
|
|
194621
194641
|
set2.status = 404;
|
|
194622
194642
|
return { error: "Document not found" };
|
|
194623
194643
|
}
|
|
194624
|
-
|
|
194625
|
-
bypassDedup: true,
|
|
194626
|
-
forceNewGeneration: true,
|
|
194627
|
-
source: "reindex"
|
|
194628
|
-
});
|
|
194629
|
-
if (enqueued !== 1) {
|
|
194644
|
+
if (result.enqueued !== 1) {
|
|
194630
194645
|
set2.status = 503;
|
|
194631
194646
|
return { error: "Failed to queue document reindex" };
|
|
194632
194647
|
}
|
|
194633
194648
|
return {
|
|
194634
194649
|
success: true,
|
|
194635
194650
|
documentId: params.docId,
|
|
194636
|
-
enqueued,
|
|
194651
|
+
enqueued: result.enqueued,
|
|
194637
194652
|
message: "Document reindex queued with a new generation"
|
|
194638
194653
|
};
|
|
194639
194654
|
} catch (err) {
|
|
@@ -229066,8 +229081,8 @@ var folderRoutes = new Elysia({ prefix: "/api/folders" }).get("/:id", async ({ p
|
|
|
229066
229081
|
set2.status = 404;
|
|
229067
229082
|
return { error: "Folder not found" };
|
|
229068
229083
|
}
|
|
229069
|
-
if (parsed.data.name !== undefined) {
|
|
229070
|
-
reembedDocsInFolder(params2.id, userId, ctx.workspaceId).catch((err) => logger3.warn({ err, folderId: params2.id }, "Failed to enqueue re-embedding for folder
|
|
229084
|
+
if (parsed.data.name !== undefined || parsed.data.parentId !== undefined || parsed.data.categoryId !== undefined) {
|
|
229085
|
+
reembedDocsInFolder(params2.id, userId, ctx.workspaceId).catch((err) => logger3.warn({ err, folderId: params2.id }, "Failed to enqueue re-embedding for folder metadata change"));
|
|
229071
229086
|
invalidateDocListCache(userId);
|
|
229072
229087
|
}
|
|
229073
229088
|
return result.updated;
|
|
@@ -229919,7 +229934,7 @@ async function resolveVisibleIds(ctx, ids, adapter, scope = _buildGraphVisibilit
|
|
|
229919
229934
|
id: documents.id,
|
|
229920
229935
|
ownerId: documents.ownerId,
|
|
229921
229936
|
visibility: documents.visibility
|
|
229922
|
-
}).from(documents).where(and(inArray(documents.id, ids), scope.kind === "admin" ? undefined : scope.kind === "public" ? eq(documents.visibility, "public") : scope.kind === "share" ? inArray(documents.id, scope.allowedDocumentIds) : or(eq(documents.ownerId, scope.ownerId), eq(documents.visibility, "public")))));
|
|
229937
|
+
}).from(documents).where(and(inArray(documents.id, ids), isNull(documents.deletedAt), scope.kind === "admin" ? undefined : scope.kind === "public" ? eq(documents.visibility, "public") : scope.kind === "share" ? inArray(documents.id, scope.allowedDocumentIds) : or(eq(documents.ownerId, scope.ownerId), eq(documents.visibility, "public")))));
|
|
229923
229938
|
return new Set(rows.filter((row) => _isGraphDocumentVisible(scope, {
|
|
229924
229939
|
id: row.id,
|
|
229925
229940
|
ownerId: row.ownerId,
|
|
@@ -230361,6 +230376,7 @@ async function retrieveExact(ctx, plan, limit, execute, scope) {
|
|
|
230361
230376
|
1.0::double precision AS score
|
|
230362
230377
|
FROM documents d
|
|
230363
230378
|
WHERE d.owner_id = ${ctx.userId}
|
|
230379
|
+
AND d.deleted_at IS NULL
|
|
230364
230380
|
${scope}
|
|
230365
230381
|
AND (
|
|
230366
230382
|
lower(trim(d.title)) = lower(trim(${plan.normalized}))
|
|
@@ -230388,6 +230404,7 @@ async function retrieveFts(ctx, plan, limit, execute, scope) {
|
|
|
230388
230404
|
ts_rank(d.search_vector, websearch_to_tsquery('english', ${plan.normalized})) AS score
|
|
230389
230405
|
FROM documents d
|
|
230390
230406
|
WHERE d.owner_id = ${ctx.userId}
|
|
230407
|
+
AND d.deleted_at IS NULL
|
|
230391
230408
|
${scope}
|
|
230392
230409
|
AND d.search_vector @@ websearch_to_tsquery('english', ${plan.normalized})
|
|
230393
230410
|
UNION ALL
|
|
@@ -230395,6 +230412,7 @@ async function retrieveFts(ctx, plan, limit, execute, scope) {
|
|
|
230395
230412
|
ts_rank(d.search_vector_simple, websearch_to_tsquery('simple', ${plan.normalized})) AS score
|
|
230396
230413
|
FROM documents d
|
|
230397
230414
|
WHERE d.owner_id = ${ctx.userId}
|
|
230415
|
+
AND d.deleted_at IS NULL
|
|
230398
230416
|
${scope}
|
|
230399
230417
|
AND d.search_vector_simple @@ websearch_to_tsquery('simple', ${plan.normalized})
|
|
230400
230418
|
)
|
|
@@ -230413,6 +230431,7 @@ async function retrieveFuzzy(ctx, plan, limit, minimum, execute, scope) {
|
|
|
230413
230431
|
similarity(d.title, ${plan.normalized})::double precision AS score
|
|
230414
230432
|
FROM documents d
|
|
230415
230433
|
WHERE d.owner_id = ${ctx.userId}
|
|
230434
|
+
AND d.deleted_at IS NULL
|
|
230416
230435
|
${scope}
|
|
230417
230436
|
AND d.title % ${plan.normalized}
|
|
230418
230437
|
AND similarity(d.title, ${plan.normalized}) >= ${minimum}
|
|
@@ -230447,6 +230466,7 @@ async function retrieveVector(ctx, plan, limit, minimum, chunkLimit, execute, pr
|
|
|
230447
230466
|
FROM document_embeddings de
|
|
230448
230467
|
JOIN documents d ON d.id = de.document_id
|
|
230449
230468
|
WHERE d.owner_id = ${ctx.userId}
|
|
230469
|
+
AND d.deleted_at IS NULL
|
|
230450
230470
|
${scope}
|
|
230451
230471
|
AND d.active_embedding_generation IS NOT NULL
|
|
230452
230472
|
AND de.generation_id = d.active_embedding_generation
|
|
@@ -231179,6 +231199,7 @@ async function hydrateResults(ctx, items, includeChunks, query, allowedDocumentI
|
|
|
231179
231199
|
JOIN documents d ON d.id = de.document_id
|
|
231180
231200
|
WHERE de.document_id IN (${sql.join(ids.map((id3) => sql`${id3}`), sql`, `)})
|
|
231181
231201
|
AND ${tenantOwnerSql("d", ctx)}
|
|
231202
|
+
AND d.deleted_at IS NULL
|
|
231182
231203
|
AND d.active_embedding_generation IS NOT NULL
|
|
231183
231204
|
AND de.generation_id = d.active_embedding_generation
|
|
231184
231205
|
AND de.is_valid = true
|
|
@@ -233301,13 +233322,13 @@ var extractionOutputSchema = exports_external2.object({
|
|
|
233301
233322
|
entities: exports_external2.array(exports_external2.unknown())
|
|
233302
233323
|
});
|
|
233303
233324
|
async function callEntityExtractionLLM(text4, options) {
|
|
233304
|
-
const primaryBase = options.llmBaseUrl ?? config3.GRAPH_EXTRACT_BASE_URL
|
|
233325
|
+
const primaryBase = options.llmBaseUrl ?? config3.GRAPH_EXTRACT_BASE_URL;
|
|
233305
233326
|
const primaryExplicitKey = options.llmApiKey ?? config3.GRAPH_EXTRACT_API_KEY;
|
|
233306
|
-
const primaryModel = options.llmModel ?? config3.GRAPH_EXTRACT_MODEL ??
|
|
233327
|
+
const primaryModel = options.llmModel ?? config3.GRAPH_EXTRACT_MODEL ?? "gpt-4o-mini";
|
|
233307
233328
|
const primaryKey = primaryBase ? resolveGraphProviderKey(primaryBase, primaryExplicitKey) : "";
|
|
233308
|
-
const fallbackBase = config3.GRAPH_EXTRACT_FALLBACK_BASE_URL
|
|
233329
|
+
const fallbackBase = config3.GRAPH_EXTRACT_FALLBACK_BASE_URL;
|
|
233309
233330
|
const fallbackExplicitKey = config3.GRAPH_EXTRACT_FALLBACK_API_KEY;
|
|
233310
|
-
const fallbackModel = config3.GRAPH_EXTRACT_FALLBACK_MODEL ??
|
|
233331
|
+
const fallbackModel = config3.GRAPH_EXTRACT_FALLBACK_MODEL ?? primaryModel;
|
|
233311
233332
|
const fallbackKey = fallbackBase ? resolveGraphProviderKey(fallbackBase, fallbackExplicitKey) : "";
|
|
233312
233333
|
const providers = [];
|
|
233313
233334
|
if (primaryBase) {
|
|
@@ -234953,7 +234974,7 @@ var swaggerConfig = {
|
|
|
234953
234974
|
},
|
|
234954
234975
|
info: {
|
|
234955
234976
|
title: "DocsMint API",
|
|
234956
|
-
version: "0.6.
|
|
234977
|
+
version: "0.6.8",
|
|
234957
234978
|
description: "Self-hosted AI-first documentation platform. Full-text + semantic search, version history, sharing, and folder organization.",
|
|
234958
234979
|
contact: { name: "HiAi-gg", url: "https://github.com/HiAi-gg/docsmint" },
|
|
234959
234980
|
license: {
|
|
@@ -234995,7 +235016,7 @@ var app = new Elysia().use(bodySizeLimit).onError(({ error: error53, set: set2 }
|
|
|
234995
235016
|
set2.headers["X-Content-Type-Options"] = "nosniff";
|
|
234996
235017
|
set2.headers["X-Frame-Options"] = "DENY";
|
|
234997
235018
|
}).use(cors({
|
|
234998
|
-
origin: config3.CORS_ORIGINS
|
|
235019
|
+
origin: config3.CORS_ORIGINS ? config3.CORS_ORIGINS.split(",").map((origin) => origin.trim()).filter(Boolean) : [config3.BETTER_AUTH_URL],
|
|
234999
235020
|
credentials: true,
|
|
235000
235021
|
maxAge: 86400
|
|
235001
235022
|
})).use(config3.NODE_ENV !== "production" ? swagger(swaggerConfig) : (e3) => e3).get("/api/health", async ({ request }) => {
|
package/package.json
CHANGED
|
@@ -24,7 +24,7 @@ import { registerSearch } from './commands/search.js';
|
|
|
24
24
|
import { registerSnapshot } from './commands/snapshot.js';
|
|
25
25
|
import { registerUpdate } from './commands/update.js';
|
|
26
26
|
|
|
27
|
-
const VERSION = '0.6.
|
|
27
|
+
const VERSION = '0.6.8';
|
|
28
28
|
|
|
29
29
|
const program = new Command();
|
|
30
30
|
program.name('hiai-docs').description('CLI for the hiai-docs knowledge base').version(VERSION);
|
|
@@ -81,7 +81,7 @@ export interface CreateDocsmintMcpServerOptions {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function createDocsmintMcpServer(options: CreateDocsmintMcpServerOptions = {}): McpServer {
|
|
84
|
-
const server = new McpServer({ name: 'docsmint', version: '0.6.
|
|
84
|
+
const server = new McpServer({ name: 'docsmint', version: '0.6.8' });
|
|
85
85
|
registerDocsmintMcpCapabilities(server, options.client ?? defaultClient);
|
|
86
86
|
return server;
|
|
87
87
|
}
|
package/server.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"source": "github",
|
|
9
9
|
"id": "1249550690"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.6.
|
|
11
|
+
"version": "0.6.8",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "@hiai-gg/docsmint",
|
|
16
|
-
"version": "0.6.
|
|
16
|
+
"version": "0.6.8",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|