@hasna/knowledge 0.2.20 → 0.2.22
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 +38 -0
- package/bin/open-knowledge-mcp.js +927 -24
- package/bin/open-knowledge.js +153 -71
- package/docs/architecture/ai-native-knowledge-base.md +16 -0
- package/package.json +1 -1
- package/src/auth.ts +123 -0
- package/src/cli.ts +153 -10
- package/src/remote-client.ts +268 -0
- package/src/service.ts +142 -0
- package/src/storage-contract.ts +28 -0
- package/src/wiki-compiler.ts +711 -0
- package/src/workspace.ts +11 -0
package/src/service.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { createArtifactStore } from './artifact-store';
|
|
2
|
+
import {
|
|
3
|
+
clearKnowledgeAuth,
|
|
4
|
+
knowledgeAuthStatus,
|
|
5
|
+
normalizeKnowledgeApiOrigin,
|
|
6
|
+
saveKnowledgeAuth,
|
|
7
|
+
type KnowledgeAuthStatus,
|
|
8
|
+
} from './auth';
|
|
2
9
|
import { runKnowledgePrompt, type KnowledgePromptOptions } from './agent';
|
|
3
10
|
import {
|
|
4
11
|
embeddingIndexStatus,
|
|
@@ -14,10 +21,12 @@ import { ingestSourceRef } from './source-ingest';
|
|
|
14
21
|
import { resolveOpenFilesSource } from './source-resolver';
|
|
15
22
|
import { providerStatus, listModelRegistry, type ProviderStatusResult, type ModelRegistryEntry } from './providers';
|
|
16
23
|
import { enqueueMissingEmbeddings, refreshEmbeddingIndex, reindexHealth, type ReindexRuntimeOptions } from './reindex';
|
|
24
|
+
import { knowledgeRegistryContract, RemoteKnowledgeClient, type RemoteKnowledgeRegistryContract } from './remote-client';
|
|
17
25
|
import { retrieveKnowledgeContext, type RetrievalOptions } from './retrieval';
|
|
18
26
|
import { hybridSearch, type HybridSearchOptions } from './search';
|
|
19
27
|
import { resolveSafetyPolicy } from './safety';
|
|
20
28
|
import { runProviderWebSearch, type WebSearchOptions } from './web-search';
|
|
29
|
+
import { compileWikiPage, fileAnswerToWiki, lintWiki, type WikiCompileOptions } from './wiki-compiler';
|
|
21
30
|
import {
|
|
22
31
|
recordStorageObjects,
|
|
23
32
|
resolveStorageContract,
|
|
@@ -30,6 +39,7 @@ import {
|
|
|
30
39
|
ensureKnowledgeWorkspace,
|
|
31
40
|
readKnowledgeConfig,
|
|
32
41
|
resolveScopedWorkspace,
|
|
42
|
+
writeKnowledgeConfig,
|
|
33
43
|
type KnowledgeConfig,
|
|
34
44
|
type KnowledgeWorkspace,
|
|
35
45
|
} from './workspace';
|
|
@@ -56,6 +66,23 @@ export interface KnowledgePathsResult {
|
|
|
56
66
|
message: string;
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
export interface KnowledgeSetupResult {
|
|
70
|
+
ok: true;
|
|
71
|
+
mode: KnowledgeConfig['mode'];
|
|
72
|
+
api_url: string | null;
|
|
73
|
+
config_path: string;
|
|
74
|
+
next: string[];
|
|
75
|
+
message: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeMode(value: string | undefined): KnowledgeConfig['mode'] | undefined {
|
|
79
|
+
if (!value) return undefined;
|
|
80
|
+
const normalized = value.trim().toLowerCase();
|
|
81
|
+
if (normalized === 'local' || normalized === 'offline') return 'local';
|
|
82
|
+
if (normalized === 'hosted' || normalized === 'remote' || normalized === 'knowledge.hasna.xyz') return 'hosted';
|
|
83
|
+
throw new Error('Invalid setup mode. Use hosted or local.');
|
|
84
|
+
}
|
|
85
|
+
|
|
59
86
|
export class KnowledgeService {
|
|
60
87
|
private ensuredWorkspace?: KnowledgeWorkspace;
|
|
61
88
|
private cachedConfig?: KnowledgeConfig;
|
|
@@ -103,6 +130,78 @@ export class KnowledgeService {
|
|
|
103
130
|
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
104
131
|
}
|
|
105
132
|
|
|
133
|
+
setup(options: { mode?: string; apiUrl?: string } = {}): KnowledgeSetupResult {
|
|
134
|
+
const workspace = this.ensureWorkspace();
|
|
135
|
+
const current = this.config();
|
|
136
|
+
const mode = normalizeMode(options.mode) ?? current.mode;
|
|
137
|
+
const apiUrl = options.apiUrl
|
|
138
|
+
? normalizeKnowledgeApiOrigin(options.apiUrl)
|
|
139
|
+
: current.hosted?.api_url
|
|
140
|
+
? normalizeKnowledgeApiOrigin(current.hosted.api_url)
|
|
141
|
+
: null;
|
|
142
|
+
const nextConfig: KnowledgeConfig = {
|
|
143
|
+
...current,
|
|
144
|
+
mode,
|
|
145
|
+
hosted: {
|
|
146
|
+
...(current.hosted ?? {}),
|
|
147
|
+
...(apiUrl ? { api_url: apiUrl } : {}),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
writeKnowledgeConfig(workspace.configPath, nextConfig);
|
|
151
|
+
this.cachedConfig = nextConfig;
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
mode,
|
|
155
|
+
api_url: nextConfig.hosted?.api_url ?? null,
|
|
156
|
+
config_path: workspace.configPath,
|
|
157
|
+
next: mode === 'hosted'
|
|
158
|
+
? ['open-knowledge auth login --api-key <key>', 'open-knowledge remote contracts --json']
|
|
159
|
+
: ['open-knowledge search <query>', 'knowledge <prompt>'],
|
|
160
|
+
message: `Set knowledge mode to ${mode}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
authStatus(env: Record<string, string | undefined> = process.env): KnowledgeAuthStatus {
|
|
165
|
+
return knowledgeAuthStatus(this.config(), env);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
saveAuth(input: {
|
|
169
|
+
apiKey: string;
|
|
170
|
+
email?: string;
|
|
171
|
+
orgId?: string;
|
|
172
|
+
orgSlug?: string;
|
|
173
|
+
userId?: string;
|
|
174
|
+
apiUrl?: string;
|
|
175
|
+
}, env: Record<string, string | undefined> = process.env) {
|
|
176
|
+
const apiUrl = input.apiUrl ?? this.config().hosted?.api_url;
|
|
177
|
+
return saveKnowledgeAuth({
|
|
178
|
+
api_key: input.apiKey,
|
|
179
|
+
email: input.email,
|
|
180
|
+
org_id: input.orgId,
|
|
181
|
+
org_slug: input.orgSlug,
|
|
182
|
+
user_id: input.userId,
|
|
183
|
+
api_url: apiUrl,
|
|
184
|
+
}, env);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
clearAuth(env: Record<string, string | undefined> = process.env) {
|
|
188
|
+
return clearKnowledgeAuth(env);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
remoteContract(): RemoteKnowledgeRegistryContract {
|
|
192
|
+
const storage = this.storageContract();
|
|
193
|
+
return knowledgeRegistryContract({
|
|
194
|
+
mode: this.config().mode,
|
|
195
|
+
sourceSchemes: this.config().sources.allowed_schemes,
|
|
196
|
+
storageType: storage.artifact_store.type,
|
|
197
|
+
artifactUriPrefix: storage.artifact_store.uri_prefix,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
remoteClient(env: Record<string, string | undefined> = process.env): RemoteKnowledgeClient | null {
|
|
202
|
+
return RemoteKnowledgeClient.fromConfig(this.config(), env);
|
|
203
|
+
}
|
|
204
|
+
|
|
106
205
|
paths(): KnowledgePathsResult {
|
|
107
206
|
const workspace = this.ensureWorkspace();
|
|
108
207
|
return {
|
|
@@ -147,6 +246,49 @@ export class KnowledgeService {
|
|
|
147
246
|
return result;
|
|
148
247
|
}
|
|
149
248
|
|
|
249
|
+
async compileWiki(options: Omit<WikiCompileOptions, 'dbPath' | 'store'> = {}) {
|
|
250
|
+
const workspace = this.ensureWorkspace();
|
|
251
|
+
return compileWikiPage({
|
|
252
|
+
...options,
|
|
253
|
+
dbPath: workspace.knowledgeDbPath,
|
|
254
|
+
store: this.artifactStore(),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async fileAnswer(options: {
|
|
259
|
+
prompt: string;
|
|
260
|
+
answer: string;
|
|
261
|
+
approveWrite?: boolean;
|
|
262
|
+
limit?: number;
|
|
263
|
+
semantic?: boolean;
|
|
264
|
+
modelRef?: string;
|
|
265
|
+
dimensions?: number;
|
|
266
|
+
fake?: boolean;
|
|
267
|
+
}) {
|
|
268
|
+
const workspace = this.ensureWorkspace();
|
|
269
|
+
const context = await this.retrieveContext({
|
|
270
|
+
query: options.prompt,
|
|
271
|
+
limit: options.limit,
|
|
272
|
+
semantic: options.semantic,
|
|
273
|
+
modelRef: options.modelRef,
|
|
274
|
+
dimensions: options.dimensions,
|
|
275
|
+
fake: options.fake,
|
|
276
|
+
});
|
|
277
|
+
return fileAnswerToWiki({
|
|
278
|
+
dbPath: workspace.knowledgeDbPath,
|
|
279
|
+
store: this.artifactStore(),
|
|
280
|
+
prompt: options.prompt,
|
|
281
|
+
answer: options.answer,
|
|
282
|
+
context,
|
|
283
|
+
approveWrite: options.approveWrite,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
lintWiki() {
|
|
288
|
+
const workspace = this.ensureWorkspace();
|
|
289
|
+
return lintWiki({ dbPath: workspace.knowledgeDbPath });
|
|
290
|
+
}
|
|
291
|
+
|
|
150
292
|
async ingestManifest(input: string) {
|
|
151
293
|
const workspace = this.ensureWorkspace();
|
|
152
294
|
return ingestOpenFilesManifest({
|
package/src/storage-contract.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import type { Database } from 'bun:sqlite';
|
|
3
|
+
import { DEFAULT_KNOWLEDGE_API_URL, normalizeKnowledgeApiOrigin } from './auth';
|
|
4
|
+
import { REMOTE_KNOWLEDGE_CONTRACT_VERSION } from './remote-client';
|
|
3
5
|
import type { KnowledgeConfig, KnowledgeWorkspace } from './workspace';
|
|
4
6
|
import { HASNA_KNOWLEDGE_APP_PATH } from './workspace';
|
|
5
7
|
|
|
@@ -34,6 +36,15 @@ export interface StorageContract {
|
|
|
34
36
|
kms_key_configured: boolean;
|
|
35
37
|
} | null;
|
|
36
38
|
};
|
|
39
|
+
hosted: {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
api_url: string;
|
|
42
|
+
api_url_env: 'KNOWLEDGE_API_URL';
|
|
43
|
+
api_key_env: 'KNOWLEDGE_API_KEY';
|
|
44
|
+
auth_storage: '~/.hasna/knowledge/auth.json';
|
|
45
|
+
remote_contract_version: typeof REMOTE_KNOWLEDGE_CONTRACT_VERSION;
|
|
46
|
+
requires_hosted_account_for_local_use: false;
|
|
47
|
+
};
|
|
37
48
|
source_ownership: {
|
|
38
49
|
owner: 'open-files';
|
|
39
50
|
preferred_ref: string;
|
|
@@ -160,6 +171,15 @@ export function resolveStorageContract(
|
|
|
160
171
|
}
|
|
161
172
|
: null,
|
|
162
173
|
},
|
|
174
|
+
hosted: {
|
|
175
|
+
enabled: config.mode === 'hosted',
|
|
176
|
+
api_url: normalizeKnowledgeApiOrigin(config.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL),
|
|
177
|
+
api_url_env: 'KNOWLEDGE_API_URL',
|
|
178
|
+
api_key_env: 'KNOWLEDGE_API_KEY',
|
|
179
|
+
auth_storage: '~/.hasna/knowledge/auth.json',
|
|
180
|
+
remote_contract_version: REMOTE_KNOWLEDGE_CONTRACT_VERSION,
|
|
181
|
+
requires_hosted_account_for_local_use: false,
|
|
182
|
+
},
|
|
163
183
|
source_ownership: {
|
|
164
184
|
owner: 'open-files',
|
|
165
185
|
preferred_ref: config.sources.preferred_ref,
|
|
@@ -219,6 +239,14 @@ export function validateStorageConfig(config: KnowledgeConfig, workspace: Knowle
|
|
|
219
239
|
errors.push('sources.allowed_schemes must include open-files.');
|
|
220
240
|
}
|
|
221
241
|
|
|
242
|
+
if (config.mode === 'hosted' && config.hosted?.api_url) {
|
|
243
|
+
try {
|
|
244
|
+
normalizeKnowledgeApiOrigin(config.hosted.api_url);
|
|
245
|
+
} catch {
|
|
246
|
+
errors.push('hosted.api_url must be an http(s) URL when mode is hosted.');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
222
250
|
return {
|
|
223
251
|
ok: errors.length === 0,
|
|
224
252
|
errors,
|