@hasna/knowledge 0.2.19 → 0.2.21
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 +22 -0
- package/bin/open-knowledge-mcp.js +345 -14
- package/bin/open-knowledge.js +82 -70
- package/docs/architecture/ai-native-knowledge-base.md +8 -0
- package/docs/architecture/hybrid-semantic-search.md +9 -0
- package/package.json +1 -1
- package/src/auth.ts +123 -0
- package/src/cli.ts +109 -4
- package/src/remote-client.ts +268 -0
- package/src/service.ts +98 -0
- package/src/storage-contract.ts +28 -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,6 +21,7 @@ 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';
|
|
@@ -30,6 +38,7 @@ import {
|
|
|
30
38
|
ensureKnowledgeWorkspace,
|
|
31
39
|
readKnowledgeConfig,
|
|
32
40
|
resolveScopedWorkspace,
|
|
41
|
+
writeKnowledgeConfig,
|
|
33
42
|
type KnowledgeConfig,
|
|
34
43
|
type KnowledgeWorkspace,
|
|
35
44
|
} from './workspace';
|
|
@@ -56,6 +65,23 @@ export interface KnowledgePathsResult {
|
|
|
56
65
|
message: string;
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
export interface KnowledgeSetupResult {
|
|
69
|
+
ok: true;
|
|
70
|
+
mode: KnowledgeConfig['mode'];
|
|
71
|
+
api_url: string | null;
|
|
72
|
+
config_path: string;
|
|
73
|
+
next: string[];
|
|
74
|
+
message: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeMode(value: string | undefined): KnowledgeConfig['mode'] | undefined {
|
|
78
|
+
if (!value) return undefined;
|
|
79
|
+
const normalized = value.trim().toLowerCase();
|
|
80
|
+
if (normalized === 'local' || normalized === 'offline') return 'local';
|
|
81
|
+
if (normalized === 'hosted' || normalized === 'remote' || normalized === 'knowledge.hasna.xyz') return 'hosted';
|
|
82
|
+
throw new Error('Invalid setup mode. Use hosted or local.');
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
export class KnowledgeService {
|
|
60
86
|
private ensuredWorkspace?: KnowledgeWorkspace;
|
|
61
87
|
private cachedConfig?: KnowledgeConfig;
|
|
@@ -103,6 +129,78 @@ export class KnowledgeService {
|
|
|
103
129
|
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
104
130
|
}
|
|
105
131
|
|
|
132
|
+
setup(options: { mode?: string; apiUrl?: string } = {}): KnowledgeSetupResult {
|
|
133
|
+
const workspace = this.ensureWorkspace();
|
|
134
|
+
const current = this.config();
|
|
135
|
+
const mode = normalizeMode(options.mode) ?? current.mode;
|
|
136
|
+
const apiUrl = options.apiUrl
|
|
137
|
+
? normalizeKnowledgeApiOrigin(options.apiUrl)
|
|
138
|
+
: current.hosted?.api_url
|
|
139
|
+
? normalizeKnowledgeApiOrigin(current.hosted.api_url)
|
|
140
|
+
: null;
|
|
141
|
+
const nextConfig: KnowledgeConfig = {
|
|
142
|
+
...current,
|
|
143
|
+
mode,
|
|
144
|
+
hosted: {
|
|
145
|
+
...(current.hosted ?? {}),
|
|
146
|
+
...(apiUrl ? { api_url: apiUrl } : {}),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
writeKnowledgeConfig(workspace.configPath, nextConfig);
|
|
150
|
+
this.cachedConfig = nextConfig;
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
mode,
|
|
154
|
+
api_url: nextConfig.hosted?.api_url ?? null,
|
|
155
|
+
config_path: workspace.configPath,
|
|
156
|
+
next: mode === 'hosted'
|
|
157
|
+
? ['open-knowledge auth login --api-key <key>', 'open-knowledge remote contracts --json']
|
|
158
|
+
: ['open-knowledge search <query>', 'knowledge <prompt>'],
|
|
159
|
+
message: `Set knowledge mode to ${mode}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
authStatus(env: Record<string, string | undefined> = process.env): KnowledgeAuthStatus {
|
|
164
|
+
return knowledgeAuthStatus(this.config(), env);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
saveAuth(input: {
|
|
168
|
+
apiKey: string;
|
|
169
|
+
email?: string;
|
|
170
|
+
orgId?: string;
|
|
171
|
+
orgSlug?: string;
|
|
172
|
+
userId?: string;
|
|
173
|
+
apiUrl?: string;
|
|
174
|
+
}, env: Record<string, string | undefined> = process.env) {
|
|
175
|
+
const apiUrl = input.apiUrl ?? this.config().hosted?.api_url;
|
|
176
|
+
return saveKnowledgeAuth({
|
|
177
|
+
api_key: input.apiKey,
|
|
178
|
+
email: input.email,
|
|
179
|
+
org_id: input.orgId,
|
|
180
|
+
org_slug: input.orgSlug,
|
|
181
|
+
user_id: input.userId,
|
|
182
|
+
api_url: apiUrl,
|
|
183
|
+
}, env);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
clearAuth(env: Record<string, string | undefined> = process.env) {
|
|
187
|
+
return clearKnowledgeAuth(env);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
remoteContract(): RemoteKnowledgeRegistryContract {
|
|
191
|
+
const storage = this.storageContract();
|
|
192
|
+
return knowledgeRegistryContract({
|
|
193
|
+
mode: this.config().mode,
|
|
194
|
+
sourceSchemes: this.config().sources.allowed_schemes,
|
|
195
|
+
storageType: storage.artifact_store.type,
|
|
196
|
+
artifactUriPrefix: storage.artifact_store.uri_prefix,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
remoteClient(env: Record<string, string | undefined> = process.env): RemoteKnowledgeClient | null {
|
|
201
|
+
return RemoteKnowledgeClient.fromConfig(this.config(), env);
|
|
202
|
+
}
|
|
203
|
+
|
|
106
204
|
paths(): KnowledgePathsResult {
|
|
107
205
|
const workspace = this.ensureWorkspace();
|
|
108
206
|
return {
|
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,
|
package/src/workspace.ts
CHANGED
|
@@ -22,6 +22,9 @@ export interface KnowledgeWorkspace {
|
|
|
22
22
|
export interface KnowledgeConfig {
|
|
23
23
|
version: 1;
|
|
24
24
|
mode: 'local' | 'hosted';
|
|
25
|
+
hosted?: {
|
|
26
|
+
api_url?: string;
|
|
27
|
+
};
|
|
25
28
|
storage: {
|
|
26
29
|
type: 'local' | 's3';
|
|
27
30
|
artifacts_root: string;
|
|
@@ -112,6 +115,9 @@ export function defaultKnowledgeConfig(): KnowledgeConfig {
|
|
|
112
115
|
return {
|
|
113
116
|
version: 1,
|
|
114
117
|
mode: 'local',
|
|
118
|
+
hosted: {
|
|
119
|
+
api_url: 'https://knowledge.hasna.xyz',
|
|
120
|
+
},
|
|
115
121
|
storage: {
|
|
116
122
|
type: 'local',
|
|
117
123
|
artifacts_root: 'artifacts',
|
|
@@ -200,3 +206,8 @@ export function readKnowledgeConfig(path: string): KnowledgeConfig {
|
|
|
200
206
|
const raw = readFileSync(path, 'utf8');
|
|
201
207
|
return JSON.parse(raw) as KnowledgeConfig;
|
|
202
208
|
}
|
|
209
|
+
|
|
210
|
+
export function writeKnowledgeConfig(path: string, config: KnowledgeConfig): void {
|
|
211
|
+
ensureParentDir(path);
|
|
212
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
213
|
+
}
|