@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/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({
@@ -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,