@hasna/knowledge 0.2.26 → 0.2.28
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 +61 -0
- package/bin/open-knowledge-mcp.js +85 -9
- package/bin/open-knowledge.js +86 -86
- package/dist/agent.d.ts +35 -0
- package/dist/artifact-store.d.ts +63 -0
- package/dist/auth.d.ts +35 -0
- package/dist/embeddings.d.ts +77 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +5709 -0
- package/dist/knowledge-db.d.ts +27 -0
- package/dist/manifest-ingest.d.ts +35 -0
- package/dist/outbox-consume.d.ts +25 -0
- package/dist/provenance.d.ts +50 -0
- package/dist/providers.d.ts +89 -0
- package/dist/reindex.d.ts +37 -0
- package/dist/remote-client.d.ts +108 -0
- package/dist/retrieval.d.ts +71 -0
- package/dist/safety.d.ts +70 -0
- package/dist/sdk.d.ts +72 -0
- package/dist/search.d.ts +65 -0
- package/dist/service.d.ts +117 -0
- package/dist/source-ingest.d.ts +18 -0
- package/dist/source-ref.d.ts +30 -0
- package/dist/source-resolver.d.ts +92 -0
- package/dist/storage-contract.d.ts +106 -0
- package/dist/web-search.d.ts +40 -0
- package/dist/wiki-compiler.d.ts +67 -0
- package/dist/wiki-layout.d.ts +23 -0
- package/dist/workspace.d.ts +111 -0
- package/docs/architecture/ai-native-knowledge-base.md +24 -0
- package/docs/architecture/hosted-wrapper-responsibilities.md +8 -0
- package/docs/canonical-secrets-bootstrap-2026-06-08.md +127 -0
- package/package.json +15 -7
- package/src/agent.ts +0 -367
- package/src/artifact-store.ts +0 -184
- package/src/auth.ts +0 -123
- package/src/cli.ts +0 -1181
- package/src/embeddings.ts +0 -516
- package/src/knowledge-db.ts +0 -354
- package/src/manifest-ingest.ts +0 -515
- package/src/mcp-http.js +0 -110
- package/src/mcp.js +0 -1503
- package/src/outbox-consume.ts +0 -463
- package/src/provenance.ts +0 -93
- package/src/providers.ts +0 -308
- package/src/reindex.ts +0 -260
- package/src/remote-client.ts +0 -268
- package/src/retrieval.ts +0 -326
- package/src/safety.ts +0 -265
- package/src/schema.js +0 -25
- package/src/search.ts +0 -510
- package/src/service.ts +0 -432
- package/src/source-ingest.ts +0 -268
- package/src/source-ref.ts +0 -104
- package/src/source-resolver.ts +0 -436
- package/src/storage-contract.ts +0 -293
- package/src/store.ts +0 -113
- package/src/web-search.ts +0 -330
- package/src/wiki-compiler.ts +0 -711
- package/src/wiki-layout.ts +0 -251
- package/src/workspace.ts +0 -213
package/src/artifact-store.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join, relative, sep } from 'node:path';
|
|
3
|
-
import type { KnowledgeConfig, KnowledgeWorkspace } from './workspace';
|
|
4
|
-
|
|
5
|
-
interface S3ClientLike {
|
|
6
|
-
send(command: unknown): Promise<any>;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ArtifactWrite {
|
|
10
|
-
key: string;
|
|
11
|
-
body: string | Uint8Array;
|
|
12
|
-
content_type?: string;
|
|
13
|
-
metadata?: Record<string, string>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ArtifactStore {
|
|
17
|
-
readonly type: 'local' | 's3';
|
|
18
|
-
readonly canRead: boolean;
|
|
19
|
-
readonly canWrite: boolean;
|
|
20
|
-
put(entry: ArtifactWrite): Promise<{ key: string; uri: string }>;
|
|
21
|
-
getText(key: string): Promise<string>;
|
|
22
|
-
exists(key: string): Promise<boolean>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function normalizeArtifactKey(key: string): string {
|
|
26
|
-
const raw = key.replace(/\\/g, '/').trim();
|
|
27
|
-
if (!raw || raw.startsWith('/')) {
|
|
28
|
-
throw new Error(`Invalid artifact key: ${key}`);
|
|
29
|
-
}
|
|
30
|
-
const segments = raw.split('/').filter(Boolean);
|
|
31
|
-
if (segments.length === 0 || segments.some((segment) => segment === '.' || segment === '..')) {
|
|
32
|
-
throw new Error(`Invalid artifact key: ${key}`);
|
|
33
|
-
}
|
|
34
|
-
return segments.join('/');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function assertInside(root: string, target: string): void {
|
|
38
|
-
const rel = relative(root, target);
|
|
39
|
-
if (rel.startsWith('..') || rel === '..' || rel.startsWith(`..${sep}`)) {
|
|
40
|
-
throw new Error(`Artifact path escapes root: ${target}`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class LocalArtifactStore implements ArtifactStore {
|
|
45
|
-
readonly type = 'local' as const;
|
|
46
|
-
readonly canRead = true;
|
|
47
|
-
readonly canWrite = true;
|
|
48
|
-
|
|
49
|
-
constructor(private readonly root: string) {
|
|
50
|
-
mkdirSync(root, { recursive: true });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async put(entry: ArtifactWrite): Promise<{ key: string; uri: string }> {
|
|
54
|
-
const key = normalizeArtifactKey(entry.key);
|
|
55
|
-
const path = join(this.root, key);
|
|
56
|
-
assertInside(this.root, path);
|
|
57
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
58
|
-
writeFileSync(path, entry.body);
|
|
59
|
-
return { key, uri: `file://${path}` };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async getText(key: string): Promise<string> {
|
|
63
|
-
const normalizedKey = normalizeArtifactKey(key);
|
|
64
|
-
const path = join(this.root, normalizedKey);
|
|
65
|
-
assertInside(this.root, path);
|
|
66
|
-
return readFileSync(path, 'utf8');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async exists(key: string): Promise<boolean> {
|
|
70
|
-
const normalizedKey = normalizeArtifactKey(key);
|
|
71
|
-
const path = join(this.root, normalizedKey);
|
|
72
|
-
assertInside(this.root, path);
|
|
73
|
-
return existsSync(path);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface S3ArtifactStoreOptions {
|
|
78
|
-
bucket: string;
|
|
79
|
-
prefix?: string;
|
|
80
|
-
region?: string;
|
|
81
|
-
profile?: string;
|
|
82
|
-
max_attempts?: number;
|
|
83
|
-
server_side_encryption?: 'AES256' | 'aws:kms';
|
|
84
|
-
kms_key_id?: string;
|
|
85
|
-
client?: S3ClientLike;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export class S3ArtifactStore implements ArtifactStore {
|
|
89
|
-
readonly type = 's3' as const;
|
|
90
|
-
readonly canRead = true;
|
|
91
|
-
readonly canWrite = true;
|
|
92
|
-
private client?: S3ClientLike;
|
|
93
|
-
|
|
94
|
-
constructor(private readonly options: S3ArtifactStoreOptions) {
|
|
95
|
-
this.client = options.client;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private async getClient(): Promise<S3ClientLike> {
|
|
99
|
-
if (this.client) return this.client;
|
|
100
|
-
const [{ S3Client }, { fromIni }] = await Promise.all([
|
|
101
|
-
import('@aws-sdk/client-s3'),
|
|
102
|
-
import('@aws-sdk/credential-providers'),
|
|
103
|
-
]);
|
|
104
|
-
this.client = new S3Client({
|
|
105
|
-
region: this.options.region,
|
|
106
|
-
credentials: this.options.profile ? fromIni({ profile: this.options.profile }) : undefined,
|
|
107
|
-
maxAttempts: this.options.max_attempts,
|
|
108
|
-
});
|
|
109
|
-
return this.client;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private objectKey(key: string): string {
|
|
113
|
-
const normalizedKey = normalizeArtifactKey(key);
|
|
114
|
-
const prefix = this.options.prefix ? normalizeArtifactKey(this.options.prefix) : '';
|
|
115
|
-
return prefix ? `${prefix}/${normalizedKey}` : normalizedKey;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async put(entry: ArtifactWrite): Promise<{ key: string; uri: string }> {
|
|
119
|
-
const [{ PutObjectCommand }, client] = await Promise.all([
|
|
120
|
-
import('@aws-sdk/client-s3'),
|
|
121
|
-
this.getClient(),
|
|
122
|
-
]);
|
|
123
|
-
const key = this.objectKey(entry.key);
|
|
124
|
-
await client.send(new PutObjectCommand({
|
|
125
|
-
Bucket: this.options.bucket,
|
|
126
|
-
Key: key,
|
|
127
|
-
Body: entry.body,
|
|
128
|
-
ContentType: entry.content_type,
|
|
129
|
-
Metadata: entry.metadata,
|
|
130
|
-
ServerSideEncryption: this.options.server_side_encryption,
|
|
131
|
-
SSEKMSKeyId: this.options.kms_key_id,
|
|
132
|
-
}));
|
|
133
|
-
return { key, uri: `s3://${this.options.bucket}/${key}` };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async getText(key: string): Promise<string> {
|
|
137
|
-
const [{ GetObjectCommand }, client] = await Promise.all([
|
|
138
|
-
import('@aws-sdk/client-s3'),
|
|
139
|
-
this.getClient(),
|
|
140
|
-
]);
|
|
141
|
-
const objectKey = this.objectKey(key);
|
|
142
|
-
const response = await client.send(new GetObjectCommand({
|
|
143
|
-
Bucket: this.options.bucket,
|
|
144
|
-
Key: objectKey,
|
|
145
|
-
}));
|
|
146
|
-
if (!response.Body) return '';
|
|
147
|
-
return await response.Body.transformToString();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async exists(key: string): Promise<boolean> {
|
|
151
|
-
const [{ HeadObjectCommand }, client] = await Promise.all([
|
|
152
|
-
import('@aws-sdk/client-s3'),
|
|
153
|
-
this.getClient(),
|
|
154
|
-
]);
|
|
155
|
-
const objectKey = this.objectKey(key);
|
|
156
|
-
try {
|
|
157
|
-
await client.send(new HeadObjectCommand({
|
|
158
|
-
Bucket: this.options.bucket,
|
|
159
|
-
Key: objectKey,
|
|
160
|
-
}));
|
|
161
|
-
return true;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
const name = error instanceof Error ? error.name : '';
|
|
164
|
-
if (name === 'NotFound' || name === 'NoSuchKey' || name === 'NotFoundError') return false;
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function createArtifactStore(config: KnowledgeConfig, workspace: KnowledgeWorkspace): ArtifactStore {
|
|
171
|
-
if (config.storage.type === 's3') {
|
|
172
|
-
if (!config.storage.s3?.bucket) throw new Error('S3 artifact storage requires storage.s3.bucket');
|
|
173
|
-
return new S3ArtifactStore({
|
|
174
|
-
bucket: config.storage.s3.bucket,
|
|
175
|
-
prefix: config.storage.s3.prefix,
|
|
176
|
-
region: config.storage.s3.region,
|
|
177
|
-
profile: config.storage.s3.profile,
|
|
178
|
-
max_attempts: config.storage.s3.max_attempts,
|
|
179
|
-
server_side_encryption: config.storage.s3.server_side_encryption,
|
|
180
|
-
kms_key_id: config.storage.s3.kms_key_id,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
return new LocalArtifactStore(workspace.artifactsDir);
|
|
184
|
-
}
|
package/src/auth.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
import type { KnowledgeConfig } from './workspace';
|
|
5
|
-
|
|
6
|
-
export interface KnowledgeAuthConfig {
|
|
7
|
-
api_key: string;
|
|
8
|
-
email?: string;
|
|
9
|
-
org_id?: string;
|
|
10
|
-
org_slug?: string;
|
|
11
|
-
user_id?: string;
|
|
12
|
-
api_url?: string;
|
|
13
|
-
created_at: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface KnowledgeAuthStatus {
|
|
17
|
-
authenticated: boolean;
|
|
18
|
-
source: 'env' | 'file' | 'none';
|
|
19
|
-
api_url: string;
|
|
20
|
-
auth_path: string;
|
|
21
|
-
email: string | null;
|
|
22
|
-
org_id: string | null;
|
|
23
|
-
org_slug: string | null;
|
|
24
|
-
user_id: string | null;
|
|
25
|
-
api_key_present: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const DEFAULT_KNOWLEDGE_API_URL = 'https://knowledge.hasna.xyz';
|
|
29
|
-
|
|
30
|
-
export function normalizeKnowledgeApiOrigin(apiUrl: string): string {
|
|
31
|
-
const url = new URL(apiUrl);
|
|
32
|
-
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
33
|
-
throw new Error('Knowledge API URL must use http or https.');
|
|
34
|
-
}
|
|
35
|
-
const pathname = url.pathname.replace(/\/+$/, '');
|
|
36
|
-
if (pathname === '/api' || pathname === '/api/v1') {
|
|
37
|
-
url.pathname = '/';
|
|
38
|
-
} else if (pathname.endsWith('/api/v1')) {
|
|
39
|
-
url.pathname = pathname.slice(0, -'/api/v1'.length) || '/';
|
|
40
|
-
} else if (pathname.endsWith('/api')) {
|
|
41
|
-
url.pathname = pathname.slice(0, -'/api'.length) || '/';
|
|
42
|
-
}
|
|
43
|
-
return url.toString().replace(/\/+$/, '');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function knowledgeAuthPath(env: Record<string, string | undefined> = process.env): string {
|
|
47
|
-
if (env.HASNA_KNOWLEDGE_AUTH_PATH) return env.HASNA_KNOWLEDGE_AUTH_PATH;
|
|
48
|
-
const root = env.HASNA_KNOWLEDGE_AUTH_DIR ?? join(homedir(), '.hasna', 'knowledge');
|
|
49
|
-
return join(root, 'auth.json');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function resolveKnowledgeApiUrl(
|
|
53
|
-
config?: KnowledgeConfig,
|
|
54
|
-
env: Record<string, string | undefined> = process.env,
|
|
55
|
-
): string {
|
|
56
|
-
return normalizeKnowledgeApiOrigin(env.KNOWLEDGE_API_URL ?? config?.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getKnowledgeAuth(env: Record<string, string | undefined> = process.env): KnowledgeAuthConfig | null {
|
|
60
|
-
try {
|
|
61
|
-
const path = knowledgeAuthPath(env);
|
|
62
|
-
if (!existsSync(path)) return null;
|
|
63
|
-
const parsed = JSON.parse(readFileSync(path, 'utf8')) as KnowledgeAuthConfig;
|
|
64
|
-
return typeof parsed.api_key === 'string' && parsed.api_key.length > 0 ? parsed : null;
|
|
65
|
-
} catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function saveKnowledgeAuth(
|
|
71
|
-
auth: Omit<KnowledgeAuthConfig, 'created_at'> & { created_at?: string },
|
|
72
|
-
env: Record<string, string | undefined> = process.env,
|
|
73
|
-
): KnowledgeAuthConfig {
|
|
74
|
-
const path = knowledgeAuthPath(env);
|
|
75
|
-
const stored: KnowledgeAuthConfig = {
|
|
76
|
-
...auth,
|
|
77
|
-
api_url: auth.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : undefined,
|
|
78
|
-
created_at: auth.created_at ?? new Date().toISOString(),
|
|
79
|
-
};
|
|
80
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
81
|
-
writeFileSync(path, `${JSON.stringify(stored, null, 2)}\n`, { mode: 0o600 });
|
|
82
|
-
return stored;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function clearKnowledgeAuth(env: Record<string, string | undefined> = process.env): boolean {
|
|
86
|
-
try {
|
|
87
|
-
unlinkSync(knowledgeAuthPath(env));
|
|
88
|
-
return true;
|
|
89
|
-
} catch {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function getKnowledgeApiKey(env: Record<string, string | undefined> = process.env): { apiKey: string | null; source: KnowledgeAuthStatus['source'] } {
|
|
95
|
-
if (env.KNOWLEDGE_API_KEY) return { apiKey: env.KNOWLEDGE_API_KEY, source: 'env' };
|
|
96
|
-
if (env.HASNA_KNOWLEDGE_API_KEY) return { apiKey: env.HASNA_KNOWLEDGE_API_KEY, source: 'env' };
|
|
97
|
-
const auth = getKnowledgeAuth(env);
|
|
98
|
-
return auth?.api_key ? { apiKey: auth.api_key, source: 'file' } : { apiKey: null, source: 'none' };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function knowledgeAuthStatus(
|
|
102
|
-
config?: KnowledgeConfig,
|
|
103
|
-
env: Record<string, string | undefined> = process.env,
|
|
104
|
-
): KnowledgeAuthStatus {
|
|
105
|
-
const auth = getKnowledgeAuth(env);
|
|
106
|
-
const key = getKnowledgeApiKey(env);
|
|
107
|
-
const apiUrl = env.KNOWLEDGE_API_URL
|
|
108
|
-
? resolveKnowledgeApiUrl(config, env)
|
|
109
|
-
: auth?.api_url
|
|
110
|
-
? normalizeKnowledgeApiOrigin(auth.api_url)
|
|
111
|
-
: resolveKnowledgeApiUrl(config, env);
|
|
112
|
-
return {
|
|
113
|
-
authenticated: Boolean(key.apiKey),
|
|
114
|
-
source: key.source,
|
|
115
|
-
api_url: apiUrl,
|
|
116
|
-
auth_path: knowledgeAuthPath(env),
|
|
117
|
-
email: key.source === 'file' ? auth?.email ?? null : null,
|
|
118
|
-
org_id: key.source === 'file' ? auth?.org_id ?? null : null,
|
|
119
|
-
org_slug: key.source === 'file' ? auth?.org_slug ?? null : null,
|
|
120
|
-
user_id: key.source === 'file' ? auth?.user_id ?? null : null,
|
|
121
|
-
api_key_present: Boolean(key.apiKey),
|
|
122
|
-
};
|
|
123
|
-
}
|