@hasna/knowledge 0.2.20 → 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/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 {
@@ -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
+ }