@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/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/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/README.md
CHANGED
|
@@ -65,6 +65,11 @@ open-knowledge paths --scope project --json
|
|
|
65
65
|
# Inspect local/S3 artifact storage and source ownership
|
|
66
66
|
open-knowledge storage status --scope project --json
|
|
67
67
|
|
|
68
|
+
# Configure optional hosted mode and inspect remote contracts
|
|
69
|
+
open-knowledge setup --mode hosted --api-url https://knowledge.hasna.xyz --scope project --json
|
|
70
|
+
open-knowledge auth whoami --scope project --json
|
|
71
|
+
open-knowledge remote contracts --scope project --json
|
|
72
|
+
|
|
68
73
|
# Initialize the project SQLite catalog
|
|
69
74
|
open-knowledge db init --scope project
|
|
70
75
|
|
|
@@ -204,6 +209,23 @@ knowledge bucket/prefix while `open-files` remains the source of truth for raw
|
|
|
204
209
|
source bytes. The command also reports artifact classes, allowed source ref
|
|
205
210
|
schemes, and warnings for non-scalable or unsafe config.
|
|
206
211
|
|
|
212
|
+
### setup / auth / remote
|
|
213
|
+
```bash
|
|
214
|
+
open-knowledge setup --mode local [--scope project] [--json]
|
|
215
|
+
open-knowledge setup --mode hosted [--api-url https://knowledge.hasna.xyz] [--scope project] [--json]
|
|
216
|
+
open-knowledge auth login --api-key <key> [--email you@example.com] [--org <slug>] [--scope project] [--json]
|
|
217
|
+
open-knowledge auth whoami [--scope project] [--json]
|
|
218
|
+
open-knowledge auth logout [--scope project] [--json]
|
|
219
|
+
open-knowledge remote status [--scope project] [--json]
|
|
220
|
+
open-knowledge remote contracts [--scope project] [--json]
|
|
221
|
+
```
|
|
222
|
+
Hosted mode mirrors the `open-skills` open-core pattern: the OSS package stays
|
|
223
|
+
local-first, while `hosted.api_url`, `KNOWLEDGE_API_URL`, and
|
|
224
|
+
`KNOWLEDGE_API_KEY` define an optional remote client boundary. Credentials are
|
|
225
|
+
stored locally in `~/.hasna/knowledge/auth.json` or supplied by env vars.
|
|
226
|
+
`remote contracts` prints the typed registry/search/ask/build/sync/status/logs
|
|
227
|
+
and artifact API contract that a future SaaS wrapper can implement.
|
|
228
|
+
|
|
207
229
|
### db
|
|
208
230
|
```bash
|
|
209
231
|
open-knowledge db init [--scope project]
|
|
@@ -13656,11 +13656,11 @@ function date4(params) {
|
|
|
13656
13656
|
// node_modules/zod/v4/classic/external.js
|
|
13657
13657
|
config(en_default());
|
|
13658
13658
|
// src/mcp.js
|
|
13659
|
-
import { existsSync as
|
|
13659
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
13660
13660
|
// package.json
|
|
13661
13661
|
var package_default = {
|
|
13662
13662
|
name: "@hasna/knowledge",
|
|
13663
|
-
version: "0.2.
|
|
13663
|
+
version: "0.2.21",
|
|
13664
13664
|
description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
|
|
13665
13665
|
type: "module",
|
|
13666
13666
|
bin: {
|
|
@@ -13761,6 +13761,9 @@ function defaultKnowledgeConfig() {
|
|
|
13761
13761
|
return {
|
|
13762
13762
|
version: 1,
|
|
13763
13763
|
mode: "local",
|
|
13764
|
+
hosted: {
|
|
13765
|
+
api_url: "https://knowledge.hasna.xyz"
|
|
13766
|
+
},
|
|
13764
13767
|
storage: {
|
|
13765
13768
|
type: "local",
|
|
13766
13769
|
artifacts_root: "artifacts"
|
|
@@ -13846,6 +13849,11 @@ function readKnowledgeConfig(path) {
|
|
|
13846
13849
|
const raw = readFileSync(path, "utf8");
|
|
13847
13850
|
return JSON.parse(raw);
|
|
13848
13851
|
}
|
|
13852
|
+
function writeKnowledgeConfig(path, config2) {
|
|
13853
|
+
ensureParentDir(path);
|
|
13854
|
+
writeFileSync(path, `${JSON.stringify(config2, null, 2)}
|
|
13855
|
+
`);
|
|
13856
|
+
}
|
|
13849
13857
|
|
|
13850
13858
|
// src/store.ts
|
|
13851
13859
|
function defaultStorePath() {
|
|
@@ -14135,6 +14143,91 @@ function createArtifactStore(config2, workspace) {
|
|
|
14135
14143
|
return new LocalArtifactStore(workspace.artifactsDir);
|
|
14136
14144
|
}
|
|
14137
14145
|
|
|
14146
|
+
// src/auth.ts
|
|
14147
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
14148
|
+
import { homedir as homedir2 } from "os";
|
|
14149
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
14150
|
+
var DEFAULT_KNOWLEDGE_API_URL = "https://knowledge.hasna.xyz";
|
|
14151
|
+
function normalizeKnowledgeApiOrigin(apiUrl) {
|
|
14152
|
+
const url2 = new URL(apiUrl);
|
|
14153
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
14154
|
+
throw new Error("Knowledge API URL must use http or https.");
|
|
14155
|
+
}
|
|
14156
|
+
const pathname = url2.pathname.replace(/\/+$/, "");
|
|
14157
|
+
if (pathname === "/api" || pathname === "/api/v1") {
|
|
14158
|
+
url2.pathname = "/";
|
|
14159
|
+
} else if (pathname.endsWith("/api/v1")) {
|
|
14160
|
+
url2.pathname = pathname.slice(0, -"/api/v1".length) || "/";
|
|
14161
|
+
} else if (pathname.endsWith("/api")) {
|
|
14162
|
+
url2.pathname = pathname.slice(0, -"/api".length) || "/";
|
|
14163
|
+
}
|
|
14164
|
+
return url2.toString().replace(/\/+$/, "");
|
|
14165
|
+
}
|
|
14166
|
+
function knowledgeAuthPath(env = process.env) {
|
|
14167
|
+
if (env.HASNA_KNOWLEDGE_AUTH_PATH)
|
|
14168
|
+
return env.HASNA_KNOWLEDGE_AUTH_PATH;
|
|
14169
|
+
const root = env.HASNA_KNOWLEDGE_AUTH_DIR ?? join3(homedir2(), ".hasna", "knowledge");
|
|
14170
|
+
return join3(root, "auth.json");
|
|
14171
|
+
}
|
|
14172
|
+
function resolveKnowledgeApiUrl(config2, env = process.env) {
|
|
14173
|
+
return normalizeKnowledgeApiOrigin(env.KNOWLEDGE_API_URL ?? config2?.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL);
|
|
14174
|
+
}
|
|
14175
|
+
function getKnowledgeAuth(env = process.env) {
|
|
14176
|
+
try {
|
|
14177
|
+
const path = knowledgeAuthPath(env);
|
|
14178
|
+
if (!existsSync4(path))
|
|
14179
|
+
return null;
|
|
14180
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
14181
|
+
return typeof parsed.api_key === "string" && parsed.api_key.length > 0 ? parsed : null;
|
|
14182
|
+
} catch {
|
|
14183
|
+
return null;
|
|
14184
|
+
}
|
|
14185
|
+
}
|
|
14186
|
+
function saveKnowledgeAuth(auth, env = process.env) {
|
|
14187
|
+
const path = knowledgeAuthPath(env);
|
|
14188
|
+
const stored = {
|
|
14189
|
+
...auth,
|
|
14190
|
+
api_url: auth.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : undefined,
|
|
14191
|
+
created_at: auth.created_at ?? new Date().toISOString()
|
|
14192
|
+
};
|
|
14193
|
+
mkdirSync3(dirname3(path), { recursive: true, mode: 448 });
|
|
14194
|
+
writeFileSync4(path, `${JSON.stringify(stored, null, 2)}
|
|
14195
|
+
`, { mode: 384 });
|
|
14196
|
+
return stored;
|
|
14197
|
+
}
|
|
14198
|
+
function clearKnowledgeAuth(env = process.env) {
|
|
14199
|
+
try {
|
|
14200
|
+
unlinkSync2(knowledgeAuthPath(env));
|
|
14201
|
+
return true;
|
|
14202
|
+
} catch {
|
|
14203
|
+
return false;
|
|
14204
|
+
}
|
|
14205
|
+
}
|
|
14206
|
+
function getKnowledgeApiKey(env = process.env) {
|
|
14207
|
+
if (env.KNOWLEDGE_API_KEY)
|
|
14208
|
+
return { apiKey: env.KNOWLEDGE_API_KEY, source: "env" };
|
|
14209
|
+
if (env.HASNA_KNOWLEDGE_API_KEY)
|
|
14210
|
+
return { apiKey: env.HASNA_KNOWLEDGE_API_KEY, source: "env" };
|
|
14211
|
+
const auth = getKnowledgeAuth(env);
|
|
14212
|
+
return auth?.api_key ? { apiKey: auth.api_key, source: "file" } : { apiKey: null, source: "none" };
|
|
14213
|
+
}
|
|
14214
|
+
function knowledgeAuthStatus(config2, env = process.env) {
|
|
14215
|
+
const auth = getKnowledgeAuth(env);
|
|
14216
|
+
const key = getKnowledgeApiKey(env);
|
|
14217
|
+
const apiUrl = env.KNOWLEDGE_API_URL ? resolveKnowledgeApiUrl(config2, env) : auth?.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : resolveKnowledgeApiUrl(config2, env);
|
|
14218
|
+
return {
|
|
14219
|
+
authenticated: Boolean(key.apiKey),
|
|
14220
|
+
source: key.source,
|
|
14221
|
+
api_url: apiUrl,
|
|
14222
|
+
auth_path: knowledgeAuthPath(env),
|
|
14223
|
+
email: key.source === "file" ? auth?.email ?? null : null,
|
|
14224
|
+
org_id: key.source === "file" ? auth?.org_id ?? null : null,
|
|
14225
|
+
org_slug: key.source === "file" ? auth?.org_slug ?? null : null,
|
|
14226
|
+
user_id: key.source === "file" ? auth?.user_id ?? null : null,
|
|
14227
|
+
api_key_present: Boolean(key.apiKey)
|
|
14228
|
+
};
|
|
14229
|
+
}
|
|
14230
|
+
|
|
14138
14231
|
// src/agent.ts
|
|
14139
14232
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
14140
14233
|
|
|
@@ -15930,7 +16023,7 @@ ${answer}`;
|
|
|
15930
16023
|
|
|
15931
16024
|
// src/outbox-consume.ts
|
|
15932
16025
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
15933
|
-
import { existsSync as
|
|
16026
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
15934
16027
|
import { basename } from "path";
|
|
15935
16028
|
|
|
15936
16029
|
// src/safety.ts
|
|
@@ -16203,9 +16296,9 @@ async function readS3Text(uri, config2, safetyPolicy) {
|
|
|
16203
16296
|
async function readOutboxInput(input, config2, safetyPolicy) {
|
|
16204
16297
|
if (input.startsWith("s3://"))
|
|
16205
16298
|
return readS3Text(input, config2, safetyPolicy);
|
|
16206
|
-
if (!
|
|
16299
|
+
if (!existsSync5(input))
|
|
16207
16300
|
throw new Error(`Outbox not found: ${input}`);
|
|
16208
|
-
return
|
|
16301
|
+
return readFileSync5(input, "utf8");
|
|
16209
16302
|
}
|
|
16210
16303
|
function mergeJson(existing, patch) {
|
|
16211
16304
|
let base = {};
|
|
@@ -16439,7 +16532,7 @@ async function consumeOpenFilesOutbox(options) {
|
|
|
16439
16532
|
|
|
16440
16533
|
// src/manifest-ingest.ts
|
|
16441
16534
|
import { createHash as createHash5 } from "crypto";
|
|
16442
|
-
import { existsSync as
|
|
16535
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
16443
16536
|
import { basename as basename2 } from "path";
|
|
16444
16537
|
function stableId4(prefix, value) {
|
|
16445
16538
|
return `${prefix}_${createHash5("sha256").update(value).digest("hex").slice(0, 20)}`;
|
|
@@ -16611,9 +16704,9 @@ async function readS3Text2(uri, config2, safetyPolicy) {
|
|
|
16611
16704
|
async function readManifestInput(input, config2, safetyPolicy) {
|
|
16612
16705
|
if (input.startsWith("s3://"))
|
|
16613
16706
|
return readS3Text2(input, config2, safetyPolicy);
|
|
16614
|
-
if (!
|
|
16707
|
+
if (!existsSync6(input))
|
|
16615
16708
|
throw new Error(`Manifest not found: ${input}`);
|
|
16616
|
-
return
|
|
16709
|
+
return readFileSync6(input, "utf8");
|
|
16617
16710
|
}
|
|
16618
16711
|
function chunkText(text, maxChars, overlapChars) {
|
|
16619
16712
|
const normalized = text.replace(/\r\n/g, `
|
|
@@ -16857,7 +16950,7 @@ async function ingestOpenFilesManifestItems(options) {
|
|
|
16857
16950
|
|
|
16858
16951
|
// src/source-ingest.ts
|
|
16859
16952
|
import { createHash as createHash6 } from "crypto";
|
|
16860
|
-
import { existsSync as
|
|
16953
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
16861
16954
|
import { basename as basename3 } from "path";
|
|
16862
16955
|
|
|
16863
16956
|
// src/source-resolver.ts
|
|
@@ -17207,9 +17300,9 @@ function titleForRef(parsed) {
|
|
|
17207
17300
|
}
|
|
17208
17301
|
async function readDirectSourceText(parsed, config2, safetyPolicy) {
|
|
17209
17302
|
if (parsed.kind === "file") {
|
|
17210
|
-
if (!
|
|
17303
|
+
if (!existsSync7(parsed.path))
|
|
17211
17304
|
throw new Error(`Source file not found: ${parsed.path}`);
|
|
17212
|
-
const text =
|
|
17305
|
+
const text = readFileSync7(parsed.path, "utf8");
|
|
17213
17306
|
return {
|
|
17214
17307
|
text,
|
|
17215
17308
|
contentSource: "file",
|
|
@@ -17560,6 +17653,164 @@ async function refreshEmbeddingIndex(options) {
|
|
|
17560
17653
|
};
|
|
17561
17654
|
}
|
|
17562
17655
|
|
|
17656
|
+
// src/remote-client.ts
|
|
17657
|
+
var REMOTE_KNOWLEDGE_CONTRACT_VERSION = 1;
|
|
17658
|
+
function isRecord(value) {
|
|
17659
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17660
|
+
}
|
|
17661
|
+
function stringValue(record2, key) {
|
|
17662
|
+
const value = record2[key];
|
|
17663
|
+
return typeof value === "string" ? value : undefined;
|
|
17664
|
+
}
|
|
17665
|
+
function numberValue(record2, key) {
|
|
17666
|
+
const value = record2[key];
|
|
17667
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
17668
|
+
}
|
|
17669
|
+
function arrayValue(record2, key) {
|
|
17670
|
+
const value = record2[key];
|
|
17671
|
+
return Array.isArray(value) ? value : undefined;
|
|
17672
|
+
}
|
|
17673
|
+
function normalizeRemoteKnowledgeRunContract(payload, fallback) {
|
|
17674
|
+
const record2 = isRecord(payload) ? payload : {};
|
|
17675
|
+
return {
|
|
17676
|
+
contract_version: REMOTE_KNOWLEDGE_CONTRACT_VERSION,
|
|
17677
|
+
id: stringValue(record2, "id") ?? fallback?.id,
|
|
17678
|
+
type: stringValue(record2, "type") ?? fallback?.type,
|
|
17679
|
+
status: stringValue(record2, "status") ?? fallback?.status,
|
|
17680
|
+
query: stringValue(record2, "query") ?? fallback?.query,
|
|
17681
|
+
prompt: stringValue(record2, "prompt") ?? fallback?.prompt,
|
|
17682
|
+
output_preview: Object.prototype.hasOwnProperty.call(record2, "output_preview") ? record2.output_preview : fallback?.output_preview,
|
|
17683
|
+
citations: arrayValue(record2, "citations") ?? fallback?.citations,
|
|
17684
|
+
artifacts: arrayValue(record2, "artifacts") ?? fallback?.artifacts,
|
|
17685
|
+
usage: isRecord(record2.usage) ? record2.usage : fallback?.usage,
|
|
17686
|
+
created_at: stringValue(record2, "created_at") ?? fallback?.created_at,
|
|
17687
|
+
started_at: stringValue(record2, "started_at") ?? fallback?.started_at,
|
|
17688
|
+
completed_at: stringValue(record2, "completed_at") ?? fallback?.completed_at,
|
|
17689
|
+
duration_ms: numberValue(record2, "duration_ms") ?? fallback?.duration_ms,
|
|
17690
|
+
error_code: stringValue(record2, "error_code") ?? fallback?.error_code,
|
|
17691
|
+
error_message: stringValue(record2, "error_message") ?? fallback?.error_message,
|
|
17692
|
+
error: stringValue(record2, "error") ?? fallback?.error,
|
|
17693
|
+
details: Object.prototype.hasOwnProperty.call(record2, "details") ? record2.details : fallback?.details
|
|
17694
|
+
};
|
|
17695
|
+
}
|
|
17696
|
+
function knowledgeRegistryContract(input) {
|
|
17697
|
+
return {
|
|
17698
|
+
contract_version: REMOTE_KNOWLEDGE_CONTRACT_VERSION,
|
|
17699
|
+
service: "open-knowledge",
|
|
17700
|
+
mode: input.mode,
|
|
17701
|
+
capabilities: [
|
|
17702
|
+
"registry",
|
|
17703
|
+
"search",
|
|
17704
|
+
"ask",
|
|
17705
|
+
"build",
|
|
17706
|
+
"sync",
|
|
17707
|
+
"status",
|
|
17708
|
+
"logs",
|
|
17709
|
+
"artifacts",
|
|
17710
|
+
"open-files-source-refs",
|
|
17711
|
+
"s3-generated-artifacts"
|
|
17712
|
+
],
|
|
17713
|
+
endpoints: {
|
|
17714
|
+
registry: "/api/v1/knowledge/registry",
|
|
17715
|
+
search: "/api/v1/knowledge/search",
|
|
17716
|
+
ask: "/api/v1/knowledge/ask",
|
|
17717
|
+
build: "/api/v1/knowledge/build",
|
|
17718
|
+
sync: "/api/v1/knowledge/sync",
|
|
17719
|
+
run_status: "/api/v1/knowledge/runs/{run_id}",
|
|
17720
|
+
run_logs: "/api/v1/knowledge/runs/{run_id}/logs",
|
|
17721
|
+
run_artifacts: "/api/v1/knowledge/runs/{run_id}/artifacts"
|
|
17722
|
+
},
|
|
17723
|
+
source_contract: {
|
|
17724
|
+
owner: "open-files",
|
|
17725
|
+
preferred_ref: "open-files",
|
|
17726
|
+
allowed_schemes: input.sourceSchemes,
|
|
17727
|
+
raw_source_bytes_stored_in_open_knowledge: false
|
|
17728
|
+
},
|
|
17729
|
+
artifact_contract: {
|
|
17730
|
+
storage_type: input.storageType,
|
|
17731
|
+
uri_prefix: input.artifactUriPrefix,
|
|
17732
|
+
generated_only: true
|
|
17733
|
+
}
|
|
17734
|
+
};
|
|
17735
|
+
}
|
|
17736
|
+
|
|
17737
|
+
class RemoteKnowledgeClient {
|
|
17738
|
+
apiKey;
|
|
17739
|
+
apiUrl;
|
|
17740
|
+
constructor(apiKey, apiUrl) {
|
|
17741
|
+
this.apiKey = apiKey;
|
|
17742
|
+
this.apiUrl = apiUrl;
|
|
17743
|
+
}
|
|
17744
|
+
static fromConfig(config2, env = process.env) {
|
|
17745
|
+
const key = getKnowledgeApiKey(env);
|
|
17746
|
+
if (!key.apiKey)
|
|
17747
|
+
return null;
|
|
17748
|
+
return new RemoteKnowledgeClient(key.apiKey, resolveKnowledgeApiUrl(config2, env));
|
|
17749
|
+
}
|
|
17750
|
+
async request(path, options = {}) {
|
|
17751
|
+
return fetch(`${this.apiUrl}${path}`, {
|
|
17752
|
+
...options,
|
|
17753
|
+
headers: {
|
|
17754
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
17755
|
+
"Content-Type": "application/json",
|
|
17756
|
+
...options.headers
|
|
17757
|
+
}
|
|
17758
|
+
});
|
|
17759
|
+
}
|
|
17760
|
+
async registry() {
|
|
17761
|
+
const response = await this.request("/api/v1/knowledge/registry");
|
|
17762
|
+
return response.json();
|
|
17763
|
+
}
|
|
17764
|
+
async search(request) {
|
|
17765
|
+
const response = await this.request("/api/v1/knowledge/search", {
|
|
17766
|
+
method: "POST",
|
|
17767
|
+
body: JSON.stringify(request)
|
|
17768
|
+
});
|
|
17769
|
+
return normalizeRemoteKnowledgeRunContract(await response.json(), { type: "search", query: request.query });
|
|
17770
|
+
}
|
|
17771
|
+
async ask(request) {
|
|
17772
|
+
const response = await this.request("/api/v1/knowledge/ask", {
|
|
17773
|
+
method: "POST",
|
|
17774
|
+
body: JSON.stringify(request)
|
|
17775
|
+
});
|
|
17776
|
+
return normalizeRemoteKnowledgeRunContract(await response.json(), { type: "ask", prompt: request.prompt });
|
|
17777
|
+
}
|
|
17778
|
+
async build(request) {
|
|
17779
|
+
const response = await this.request("/api/v1/knowledge/build", {
|
|
17780
|
+
method: "POST",
|
|
17781
|
+
body: JSON.stringify(request)
|
|
17782
|
+
});
|
|
17783
|
+
return normalizeRemoteKnowledgeRunContract(await response.json(), { type: "build", prompt: request.prompt });
|
|
17784
|
+
}
|
|
17785
|
+
async sync(request = {}) {
|
|
17786
|
+
const response = await this.request("/api/v1/knowledge/sync", {
|
|
17787
|
+
method: "POST",
|
|
17788
|
+
body: JSON.stringify(request)
|
|
17789
|
+
});
|
|
17790
|
+
return normalizeRemoteKnowledgeRunContract(await response.json(), { type: "sync" });
|
|
17791
|
+
}
|
|
17792
|
+
async runStatus(runId) {
|
|
17793
|
+
const response = await this.request(`/api/v1/knowledge/runs/${encodeURIComponent(runId)}`);
|
|
17794
|
+
if (!response.ok)
|
|
17795
|
+
return null;
|
|
17796
|
+
return normalizeRemoteKnowledgeRunContract(await response.json(), { id: runId, type: "status" });
|
|
17797
|
+
}
|
|
17798
|
+
async runLogs(runId) {
|
|
17799
|
+
const response = await this.request(`/api/v1/knowledge/runs/${encodeURIComponent(runId)}/logs`);
|
|
17800
|
+
if (!response.ok)
|
|
17801
|
+
return [];
|
|
17802
|
+
const payload = await response.json();
|
|
17803
|
+
return Array.isArray(payload) ? payload : [];
|
|
17804
|
+
}
|
|
17805
|
+
async runArtifacts(runId) {
|
|
17806
|
+
const response = await this.request(`/api/v1/knowledge/runs/${encodeURIComponent(runId)}/artifacts`);
|
|
17807
|
+
if (!response.ok)
|
|
17808
|
+
return [];
|
|
17809
|
+
const payload = await response.json();
|
|
17810
|
+
return Array.isArray(payload) ? payload : [];
|
|
17811
|
+
}
|
|
17812
|
+
}
|
|
17813
|
+
|
|
17563
17814
|
// src/web-search.ts
|
|
17564
17815
|
import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
|
|
17565
17816
|
function stableHash(value) {
|
|
@@ -17895,6 +18146,15 @@ function resolveStorageContract(config2, workspace, scope = "global") {
|
|
|
17895
18146
|
kms_key_configured: Boolean(s3.kms_key_id)
|
|
17896
18147
|
} : null
|
|
17897
18148
|
},
|
|
18149
|
+
hosted: {
|
|
18150
|
+
enabled: config2.mode === "hosted",
|
|
18151
|
+
api_url: normalizeKnowledgeApiOrigin(config2.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL),
|
|
18152
|
+
api_url_env: "KNOWLEDGE_API_URL",
|
|
18153
|
+
api_key_env: "KNOWLEDGE_API_KEY",
|
|
18154
|
+
auth_storage: "~/.hasna/knowledge/auth.json",
|
|
18155
|
+
remote_contract_version: REMOTE_KNOWLEDGE_CONTRACT_VERSION,
|
|
18156
|
+
requires_hosted_account_for_local_use: false
|
|
18157
|
+
},
|
|
17898
18158
|
source_ownership: {
|
|
17899
18159
|
owner: "open-files",
|
|
17900
18160
|
preferred_ref: config2.sources.preferred_ref,
|
|
@@ -17950,6 +18210,13 @@ function validateStorageConfig(config2, workspace) {
|
|
|
17950
18210
|
if (!config2.sources.allowed_schemes.includes("open-files")) {
|
|
17951
18211
|
errors3.push("sources.allowed_schemes must include open-files.");
|
|
17952
18212
|
}
|
|
18213
|
+
if (config2.mode === "hosted" && config2.hosted?.api_url) {
|
|
18214
|
+
try {
|
|
18215
|
+
normalizeKnowledgeApiOrigin(config2.hosted.api_url);
|
|
18216
|
+
} catch {
|
|
18217
|
+
errors3.push("hosted.api_url must be an http(s) URL when mode is hosted.");
|
|
18218
|
+
}
|
|
18219
|
+
}
|
|
17953
18220
|
return {
|
|
17954
18221
|
ok: errors3.length === 0,
|
|
17955
18222
|
errors: errors3,
|
|
@@ -18188,6 +18455,17 @@ function recordWikiLayoutCatalog(db, artifacts, now = new Date) {
|
|
|
18188
18455
|
}
|
|
18189
18456
|
|
|
18190
18457
|
// src/service.ts
|
|
18458
|
+
function normalizeMode(value) {
|
|
18459
|
+
if (!value)
|
|
18460
|
+
return;
|
|
18461
|
+
const normalized = value.trim().toLowerCase();
|
|
18462
|
+
if (normalized === "local" || normalized === "offline")
|
|
18463
|
+
return "local";
|
|
18464
|
+
if (normalized === "hosted" || normalized === "remote" || normalized === "knowledge.hasna.xyz")
|
|
18465
|
+
return "hosted";
|
|
18466
|
+
throw new Error("Invalid setup mode. Use hosted or local.");
|
|
18467
|
+
}
|
|
18468
|
+
|
|
18191
18469
|
class KnowledgeService {
|
|
18192
18470
|
options;
|
|
18193
18471
|
ensuredWorkspace;
|
|
@@ -18228,6 +18506,59 @@ class KnowledgeService {
|
|
|
18228
18506
|
validateStorage() {
|
|
18229
18507
|
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
18230
18508
|
}
|
|
18509
|
+
setup(options = {}) {
|
|
18510
|
+
const workspace = this.ensureWorkspace();
|
|
18511
|
+
const current = this.config();
|
|
18512
|
+
const mode = normalizeMode(options.mode) ?? current.mode;
|
|
18513
|
+
const apiUrl = options.apiUrl ? normalizeKnowledgeApiOrigin(options.apiUrl) : current.hosted?.api_url ? normalizeKnowledgeApiOrigin(current.hosted.api_url) : null;
|
|
18514
|
+
const nextConfig = {
|
|
18515
|
+
...current,
|
|
18516
|
+
mode,
|
|
18517
|
+
hosted: {
|
|
18518
|
+
...current.hosted ?? {},
|
|
18519
|
+
...apiUrl ? { api_url: apiUrl } : {}
|
|
18520
|
+
}
|
|
18521
|
+
};
|
|
18522
|
+
writeKnowledgeConfig(workspace.configPath, nextConfig);
|
|
18523
|
+
this.cachedConfig = nextConfig;
|
|
18524
|
+
return {
|
|
18525
|
+
ok: true,
|
|
18526
|
+
mode,
|
|
18527
|
+
api_url: nextConfig.hosted?.api_url ?? null,
|
|
18528
|
+
config_path: workspace.configPath,
|
|
18529
|
+
next: mode === "hosted" ? ["open-knowledge auth login --api-key <key>", "open-knowledge remote contracts --json"] : ["open-knowledge search <query>", "knowledge <prompt>"],
|
|
18530
|
+
message: `Set knowledge mode to ${mode}`
|
|
18531
|
+
};
|
|
18532
|
+
}
|
|
18533
|
+
authStatus(env = process.env) {
|
|
18534
|
+
return knowledgeAuthStatus(this.config(), env);
|
|
18535
|
+
}
|
|
18536
|
+
saveAuth(input, env = process.env) {
|
|
18537
|
+
const apiUrl = input.apiUrl ?? this.config().hosted?.api_url;
|
|
18538
|
+
return saveKnowledgeAuth({
|
|
18539
|
+
api_key: input.apiKey,
|
|
18540
|
+
email: input.email,
|
|
18541
|
+
org_id: input.orgId,
|
|
18542
|
+
org_slug: input.orgSlug,
|
|
18543
|
+
user_id: input.userId,
|
|
18544
|
+
api_url: apiUrl
|
|
18545
|
+
}, env);
|
|
18546
|
+
}
|
|
18547
|
+
clearAuth(env = process.env) {
|
|
18548
|
+
return clearKnowledgeAuth(env);
|
|
18549
|
+
}
|
|
18550
|
+
remoteContract() {
|
|
18551
|
+
const storage = this.storageContract();
|
|
18552
|
+
return knowledgeRegistryContract({
|
|
18553
|
+
mode: this.config().mode,
|
|
18554
|
+
sourceSchemes: this.config().sources.allowed_schemes,
|
|
18555
|
+
storageType: storage.artifact_store.type,
|
|
18556
|
+
artifactUriPrefix: storage.artifact_store.uri_prefix
|
|
18557
|
+
});
|
|
18558
|
+
}
|
|
18559
|
+
remoteClient(env = process.env) {
|
|
18560
|
+
return RemoteKnowledgeClient.fromConfig(this.config(), env);
|
|
18561
|
+
}
|
|
18231
18562
|
paths() {
|
|
18232
18563
|
const workspace = this.ensureWorkspace();
|
|
18233
18564
|
return {
|
|
@@ -18970,7 +19301,7 @@ function buildServer() {
|
|
|
18970
19301
|
const storePath = resolveStorePath(store_path, scope);
|
|
18971
19302
|
return readStoreLocked(storePath, (db) => {
|
|
18972
19303
|
const filePath = file2 || "./knowledge-export.json";
|
|
18973
|
-
|
|
19304
|
+
writeFileSync5(filePath, JSON.stringify(db, null, 2));
|
|
18974
19305
|
return jsonText({ ok: true, file: filePath, count: db.items.length });
|
|
18975
19306
|
});
|
|
18976
19307
|
});
|
|
@@ -18979,9 +19310,9 @@ function buildServer() {
|
|
|
18979
19310
|
store_path: storePathField,
|
|
18980
19311
|
scope: scopeField
|
|
18981
19312
|
}, async ({ file: file2, store_path, scope }) => {
|
|
18982
|
-
if (!
|
|
19313
|
+
if (!existsSync8(file2))
|
|
18983
19314
|
return errorText(`File not found: ${file2}`);
|
|
18984
|
-
const imported = JSON.parse(
|
|
19315
|
+
const imported = JSON.parse(readFileSync8(file2, "utf8"));
|
|
18985
19316
|
if (!imported || !Array.isArray(imported.items))
|
|
18986
19317
|
return errorText('Invalid import file: expected {"items": [...]}');
|
|
18987
19318
|
const storePath = resolveStorePath(store_path, scope);
|