@hasna/knowledge 0.2.24 → 0.2.26
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 +15 -0
- package/bin/open-knowledge-mcp.js +1 -1
- package/bin/open-knowledge.js +1 -1
- package/docs/architecture/ai-native-knowledge-base.md +7 -0
- package/docs/architecture/hosted-wrapper-responsibilities.md +177 -0
- package/docs/examples/company-wiki-workflow.md +176 -0
- package/docs/migration/json-to-sqlite.md +151 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,6 +122,21 @@ knowledge "How do we cite handbook policy?" --scope project --json
|
|
|
122
122
|
HASNA_KNOWLEDGE_WEB_SEARCH=1 open-knowledge web search "latest AI SDK web search" --provider openai --json
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
## Guides
|
|
126
|
+
|
|
127
|
+
- [Company wiki workflow](docs/examples/company-wiki-workflow.md): an end-to-end
|
|
128
|
+
local workflow for open-files manifests, search, prompt runs, cited wiki
|
|
129
|
+
pages, linting, reindexing, MCP, and optional hosted/S3 mode.
|
|
130
|
+
- [JSON to SQLite migration](docs/migration/json-to-sqlite.md): how legacy
|
|
131
|
+
JSON notes coexist with the `.hasna/apps/knowledge` workspace and the
|
|
132
|
+
versioned SQLite catalog.
|
|
133
|
+
- [AI-native architecture](docs/architecture/ai-native-knowledge-base.md):
|
|
134
|
+
source boundaries, wiki model, search model, provider registry, and non-goals.
|
|
135
|
+
- [Hybrid semantic search](docs/architecture/hybrid-semantic-search.md):
|
|
136
|
+
keyword/vector/search-context contracts and hosted index options.
|
|
137
|
+
- [Hosted wrapper responsibilities](docs/architecture/hosted-wrapper-responsibilities.md):
|
|
138
|
+
what a future SaaS layer owns outside the OSS package.
|
|
139
|
+
|
|
125
140
|
## Commands
|
|
126
141
|
|
|
127
142
|
### add
|
|
@@ -13660,7 +13660,7 @@ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync
|
|
|
13660
13660
|
// package.json
|
|
13661
13661
|
var package_default = {
|
|
13662
13662
|
name: "@hasna/knowledge",
|
|
13663
|
-
version: "0.2.
|
|
13663
|
+
version: "0.2.26",
|
|
13664
13664
|
description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
|
|
13665
13665
|
type: "module",
|
|
13666
13666
|
bin: {
|
package/bin/open-knowledge.js
CHANGED
|
@@ -622,7 +622,7 @@ Pages should be concise, cited, and organized for both humans and agents.
|
|
|
622
622
|
content_hash = excluded.content_hash,
|
|
623
623
|
status = excluded.status,
|
|
624
624
|
metadata_json = excluded.metadata_json,
|
|
625
|
-
updated_at = excluded.updated_at`,[u,"wiki/README.md","Wiki",s.uri,s.hash??null,"active",JSON.stringify({artifact_key:s.key,provenance:Et(s)}),r,r]),lo(e,u,"Wiki",s,Jn(),r)}}function _o(e){if(!e)return;let t=e.trim().toLowerCase();if(t==="local"||t==="offline")return"local";if(t==="hosted"||t==="remote"||t==="knowledge.hasna.xyz")return"hosted";throw Error("Invalid setup mode. Use hosted or local.")}class Yn{options;ensuredWorkspace;cachedConfig;constructor(e={}){this.options=e}get scope(){return this.options.scope??"global"}get workspace(){return this.ensuredWorkspace??wt(this.options.scope,this.options.cwd)}ensureWorkspace(){if(!this.ensuredWorkspace)this.ensuredWorkspace=bt(this.workspace.home);return this.ensuredWorkspace}jsonStorePath(){return this.ensureWorkspace().jsonStorePath}config(){if(!this.cachedConfig){let e=this.ensureWorkspace();this.cachedConfig=vt(e.configPath)}return this.cachedConfig}safetyPolicy(){return pn(this.config(),this.ensureWorkspace())}artifactStore(){return Lt(this.config(),this.ensureWorkspace())}storageContract(){return Kn(this.config(),this.ensureWorkspace(),this.scope)}validateStorage(){return gt(this.config(),this.ensureWorkspace())}setup(e={}){let t=this.ensureWorkspace(),n=this.config(),r=_o(e.mode)??n.mode,i=e.apiUrl?$(e.apiUrl):n.hosted?.api_url?$(n.hosted.api_url):null,s={...n,mode:r,hosted:{...n.hosted??{},...i?{api_url:i}:{}}};return St(t.configPath,s),this.cachedConfig=s,{ok:!0,mode:r,api_url:s.hosted?.api_url??null,config_path:t.configPath,next:r==="hosted"?["open-knowledge auth login --api-key <key>","open-knowledge remote contracts --json"]:["open-knowledge search <query>","knowledge <prompt>"],message:`Set knowledge mode to ${r}`}}authStatus(e=process.env){return Kt(this.config(),e)}saveAuth(e,t=process.env){let n=e.apiUrl??this.config().hosted?.api_url;return Dt({api_key:e.apiKey,email:e.email,org_id:e.orgId,org_slug:e.orgSlug,user_id:e.userId,api_url:n},t)}clearAuth(e=process.env){return Ut(e)}remoteContract(){let e=this.storageContract();return An({mode:this.config().mode,sourceSchemes:this.config().sources.allowed_schemes,storageType:e.artifact_store.type,artifactUriPrefix:e.artifact_store.uri_prefix})}remoteClient(e=process.env){return We.fromConfig(this.config(),e)}paths(){let e=this.ensureWorkspace();return{ok:!0,scope:this.scope,home:e.home,config_path:e.configPath,json_store_path:e.jsonStorePath,knowledge_db_path:e.knowledgeDbPath,artifacts_dir:e.artifactsDir,indexes_dir:e.indexesDir,logs_dir:e.logsDir,runs_dir:e.runsDir,schemas_dir:e.schemasDir,wiki_dir:e.wikiDir,config:this.config(),message:e.home}}initDb(){return x(this.ensureWorkspace().knowledgeDbPath)}dbStats(){let e=this.ensureWorkspace();return x(e.knowledgeDbPath),Ot(e.knowledgeDbPath)}async initWiki(){let e=this.ensureWorkspace();x(e.knowledgeDbPath);let t=await zn(this.artifactStore()),n=b(e.knowledgeDbPath);try{ge(n,t.artifacts),Gn(n,t.artifacts)}finally{n.close()}return t}async compileWiki(e={}){let t=this.ensureWorkspace();return qn({...e,dbPath:t.knowledgeDbPath,store:this.artifactStore()})}async fileAnswer(e){let t=this.ensureWorkspace(),n=await this.retrieveContext({query:e.prompt,limit:e.limit,semantic:e.semantic,modelRef:e.modelRef,dimensions:e.dimensions,fake:e.fake});return Hn({dbPath:t.knowledgeDbPath,store:this.artifactStore(),prompt:e.prompt,answer:e.answer,context:n,approveWrite:e.approveWrite})}lintWiki(){let e=this.ensureWorkspace();return Bn({dbPath:e.knowledgeDbPath})}async ingestManifest(e){let t=this.ensureWorkspace();return yn({dbPath:t.knowledgeDbPath,input:e,config:this.config(),safetyPolicy:this.safetyPolicy()})}async ingestSource(e,t){let n=this.ensureWorkspace();return vn({dbPath:n.knowledgeDbPath,sourceRef:e,purpose:t,config:this.config(),safetyPolicy:this.safetyPolicy()})}async resolveSource(e,t={}){let n=this.ensureWorkspace();return Ke({dbPath:n.knowledgeDbPath,sourceRef:e,purpose:t.purpose,limit:t.limit,safetyPolicy:this.safetyPolicy()})}async consumeOutbox(e){let t=this.ensureWorkspace();return kn({dbPath:t.knowledgeDbPath,input:e,config:this.config(),safetyPolicy:this.safetyPolicy()})}reindexHealth(e={}){let t=this.ensureWorkspace();return Rn({...e,dbPath:t.knowledgeDbPath,config:this.config()})}enqueueReindex(e={}){let t=this.ensureWorkspace();return ft({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async refreshEmbeddings(e={}){let t=this.ensureWorkspace();return xn({...e,dbPath:t.knowledgeDbPath,config:this.config()})}providerStatus(e=process.env){return $t(this.config(),e)}modelRegistry(){return rt(this.config())}embeddingStatus(){let e=this.ensureWorkspace();return Yt(e.knowledgeDbPath)}async indexEmbeddings(e={}){let t=this.ensureWorkspace();return Te({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async semanticSearch(e){let t=this.ensureWorkspace();return Re({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async search(e){let t=this.ensureWorkspace();return Ae({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async retrieveContext(e){let t=this.ensureWorkspace();return Le({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async runPrompt(e){let t=this.ensureWorkspace();return un({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async webSearch(e){let t=this.ensureWorkspace();return Pn({...e,dbPath:t.knowledgeDbPath,config:this.config(),safetyPolicy:this.safetyPolicy()})}}function Vn(e={}){return new Yn(e)}import{basename as go}from"path";var pe={name:"@hasna/knowledge",version:"0.2.24",description:"Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",type:"module",bin:{knowledge:"bin/open-knowledge.js","open-knowledge":"bin/open-knowledge.js","open-knowledge-mcp":"bin/open-knowledge-mcp.js"},files:["bin","src","docs","LICENSE","README.md"],scripts:{test:"bun test","test:cli":"bun test tests/cli.test.ts",build:"bun build --target=bun --outfile=bin/open-knowledge.js --minify --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers --external ai --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/deepseek src/cli.ts && bun build --target=bun --outfile=bin/open-knowledge-mcp.js --external @modelcontextprotocol/sdk --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers --external ai --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/deepseek src/mcp.js",prepublishOnly:"bun run build",postinstall:"bun run build"},keywords:["knowledge","cli","agents","json","notes","local","store"],license:"Apache-2.0",publishConfig:{registry:"https://registry.npmjs.org",access:"public"},repository:{type:"git",url:"git+https://github.com/hasna/knowledge.git"},bugs:{url:"https://github.com/hasna/knowledge/issues"},author:"Hasna Inc. <hasna@example.com>",engines:{bun:">=1.0",node:">=18"},dependencies:{"@aws-sdk/client-s3":"^3.1063.0","@aws-sdk/credential-providers":"^3.1063.0","@ai-sdk/anthropic":"^3.0.81","@ai-sdk/deepseek":"^2.0.35","@ai-sdk/openai":"^3.0.68","@modelcontextprotocol/sdk":"^1.29.0",ai:"^6.0.197",zod:"^4.3.6"},devDependencies:{"@types/bun":"^1.3.14"}};var Qn={debug:0,info:1,warn:2,error:3},po=()=>{if(process.env.DEBUG)return"debug";if(process.env.LOG_LEVEL==="debug")return"debug";if(process.env.LOG_LEVEL==="warn")return"warn";if(process.env.LOG_LEVEL==="error")return"error";return"info"};function ee(e,t,n){if(Qn[e]<Qn[po()])return;let r={debug:"[DEBUG]",info:"[INFO]",warn:"[WARN]",error:"[ERROR]"}[e],i=n?`${r} ${t} ${JSON.stringify(n)}`:`${r} ${t}`;if(e==="error")console.error(i);else console.error(i)}var Zn=["add","list","get","delete","update","archive","restore","upsert","untag","export","prune","dedupe","stats","paths","setup","auth","remote","storage","db","wiki","source","ingest","reindex","search","web","ask","build","embeddings","providers","safety","help"],er={ls:"list",rm:"delete",edit:"update",unarchive:"restore",knowledge:"ask"};function mo(e){let t=[],n={};for(let r=0;r<e.length;r+=1){let i=e[r];if(!i.startsWith("-")){t.push(i);continue}switch(i){case"--json":n.json=!0;break;case"--yes":case"-y":n.yes=!0;break;case"--help":case"-h":n.help=!0;break;case"--version":case"-v":n.version=!0;break;case"--desc":n.desc=!0;break;case"--page":case"-p":n.page=Number(e[r+1]),r+=1;break;case"--limit":case"-l":n.limit=Number(e[r+1]),r+=1;break;case"--search":case"-s":n.search=e[r+1],r+=1;break;case"--sort":n.sort=e[r+1],r+=1;break;case"--id":n.id=e[r+1],r+=1;break;case"--store":n.store=e[r+1],r+=1;break;case"--title":n.title=e[r+1],r+=1;break;case"--content":n.content=e[r+1],r+=1;break;case"--url":n.url=e[r+1],r+=1;break;case"--tag":case"-t":n.tag=e[r+1],r+=1;break;case"--format":n.format=e[r+1],r+=1;break;case"--completions":n.completions=e[r+1],r+=1;break;case"--purpose":n.purpose=e[r+1],r+=1;break;case"--model":n.model=e[r+1],r+=1;break;case"--dimensions":n.dimensions=Number(e[r+1]),r+=1;break;case"--semantic":n.semantic=!0;break;case"--context":n.context=!0;break;case"--generate":n.generate=!0;break;case"--approve-write":n.approveWrite=!0;break;case"--provider":n.provider=e[r+1],r+=1;break;case"--mode":n.mode=e[r+1],r+=1;break;case"--api-url":n.apiUrl=e[r+1],r+=1;break;case"--api-key":n.apiKey=e[r+1],r+=1;break;case"--email":n.email=e[r+1],r+=1;break;case"--org":n.org=e[r+1],r+=1;break;case"--org-id":n.orgId=e[r+1],r+=1;break;case"--user-id":n.userId=e[r+1],r+=1;break;case"--domain":n.domain=[...n.domain??[],e[r+1]],r+=1;break;case"--file-results":n.fileResults=!0;break;case"--full":n.full=!0;break;case"--fake":n.fake=!0;break;case"--no-color":n.noColor=!0;break;case"--scope":n.scope=e[r+1],r+=1;break;case"--older-than":n.olderThan=Number(e[r+1]),r+=1;break;case"--empty":n.empty=!0;break;case"--archived":n.archived=!0;break;case"--include-archived":n.includeArchived=!0;break;default:throw Error(`Unknown flag: ${i}. Run 'open-knowledge --help' for valid options.`)}}return{positional:t,flags:n}}function ho(e){if(!e)return"";return er[e]??e}function Eo(e,t){let n=Array.from({length:e.length+1},()=>Array(t.length+1).fill(0));for(let r=0;r<=e.length;r+=1)n[r][0]=r;for(let r=0;r<=t.length;r+=1)n[0][r]=r;for(let r=1;r<=e.length;r+=1)for(let i=1;i<=t.length;i+=1){let s=e[r-1]===t[i-1]?0:1;n[r][i]=Math.min(n[r-1][i]+1,n[r][i-1]+1,n[r-1][i-1]+s)}return n[e.length][t.length]}function ko(e){if(!e)return"";let t=[...Zn,...Object.keys(er)],n="",r=Number.POSITIVE_INFINITY;for(let i of t){let s=Eo(e,i);if(s<r)r=s,n=i}return r<=3?n:""}function yo(){return go(process.argv[1]??"")==="knowledge"}function bo(){console.log(`open-knowledge - local agent knowledge store
|
|
625
|
+
updated_at = excluded.updated_at`,[u,"wiki/README.md","Wiki",s.uri,s.hash??null,"active",JSON.stringify({artifact_key:s.key,provenance:Et(s)}),r,r]),lo(e,u,"Wiki",s,Jn(),r)}}function _o(e){if(!e)return;let t=e.trim().toLowerCase();if(t==="local"||t==="offline")return"local";if(t==="hosted"||t==="remote"||t==="knowledge.hasna.xyz")return"hosted";throw Error("Invalid setup mode. Use hosted or local.")}class Yn{options;ensuredWorkspace;cachedConfig;constructor(e={}){this.options=e}get scope(){return this.options.scope??"global"}get workspace(){return this.ensuredWorkspace??wt(this.options.scope,this.options.cwd)}ensureWorkspace(){if(!this.ensuredWorkspace)this.ensuredWorkspace=bt(this.workspace.home);return this.ensuredWorkspace}jsonStorePath(){return this.ensureWorkspace().jsonStorePath}config(){if(!this.cachedConfig){let e=this.ensureWorkspace();this.cachedConfig=vt(e.configPath)}return this.cachedConfig}safetyPolicy(){return pn(this.config(),this.ensureWorkspace())}artifactStore(){return Lt(this.config(),this.ensureWorkspace())}storageContract(){return Kn(this.config(),this.ensureWorkspace(),this.scope)}validateStorage(){return gt(this.config(),this.ensureWorkspace())}setup(e={}){let t=this.ensureWorkspace(),n=this.config(),r=_o(e.mode)??n.mode,i=e.apiUrl?$(e.apiUrl):n.hosted?.api_url?$(n.hosted.api_url):null,s={...n,mode:r,hosted:{...n.hosted??{},...i?{api_url:i}:{}}};return St(t.configPath,s),this.cachedConfig=s,{ok:!0,mode:r,api_url:s.hosted?.api_url??null,config_path:t.configPath,next:r==="hosted"?["open-knowledge auth login --api-key <key>","open-knowledge remote contracts --json"]:["open-knowledge search <query>","knowledge <prompt>"],message:`Set knowledge mode to ${r}`}}authStatus(e=process.env){return Kt(this.config(),e)}saveAuth(e,t=process.env){let n=e.apiUrl??this.config().hosted?.api_url;return Dt({api_key:e.apiKey,email:e.email,org_id:e.orgId,org_slug:e.orgSlug,user_id:e.userId,api_url:n},t)}clearAuth(e=process.env){return Ut(e)}remoteContract(){let e=this.storageContract();return An({mode:this.config().mode,sourceSchemes:this.config().sources.allowed_schemes,storageType:e.artifact_store.type,artifactUriPrefix:e.artifact_store.uri_prefix})}remoteClient(e=process.env){return We.fromConfig(this.config(),e)}paths(){let e=this.ensureWorkspace();return{ok:!0,scope:this.scope,home:e.home,config_path:e.configPath,json_store_path:e.jsonStorePath,knowledge_db_path:e.knowledgeDbPath,artifacts_dir:e.artifactsDir,indexes_dir:e.indexesDir,logs_dir:e.logsDir,runs_dir:e.runsDir,schemas_dir:e.schemasDir,wiki_dir:e.wikiDir,config:this.config(),message:e.home}}initDb(){return x(this.ensureWorkspace().knowledgeDbPath)}dbStats(){let e=this.ensureWorkspace();return x(e.knowledgeDbPath),Ot(e.knowledgeDbPath)}async initWiki(){let e=this.ensureWorkspace();x(e.knowledgeDbPath);let t=await zn(this.artifactStore()),n=b(e.knowledgeDbPath);try{ge(n,t.artifacts),Gn(n,t.artifacts)}finally{n.close()}return t}async compileWiki(e={}){let t=this.ensureWorkspace();return qn({...e,dbPath:t.knowledgeDbPath,store:this.artifactStore()})}async fileAnswer(e){let t=this.ensureWorkspace(),n=await this.retrieveContext({query:e.prompt,limit:e.limit,semantic:e.semantic,modelRef:e.modelRef,dimensions:e.dimensions,fake:e.fake});return Hn({dbPath:t.knowledgeDbPath,store:this.artifactStore(),prompt:e.prompt,answer:e.answer,context:n,approveWrite:e.approveWrite})}lintWiki(){let e=this.ensureWorkspace();return Bn({dbPath:e.knowledgeDbPath})}async ingestManifest(e){let t=this.ensureWorkspace();return yn({dbPath:t.knowledgeDbPath,input:e,config:this.config(),safetyPolicy:this.safetyPolicy()})}async ingestSource(e,t){let n=this.ensureWorkspace();return vn({dbPath:n.knowledgeDbPath,sourceRef:e,purpose:t,config:this.config(),safetyPolicy:this.safetyPolicy()})}async resolveSource(e,t={}){let n=this.ensureWorkspace();return Ke({dbPath:n.knowledgeDbPath,sourceRef:e,purpose:t.purpose,limit:t.limit,safetyPolicy:this.safetyPolicy()})}async consumeOutbox(e){let t=this.ensureWorkspace();return kn({dbPath:t.knowledgeDbPath,input:e,config:this.config(),safetyPolicy:this.safetyPolicy()})}reindexHealth(e={}){let t=this.ensureWorkspace();return Rn({...e,dbPath:t.knowledgeDbPath,config:this.config()})}enqueueReindex(e={}){let t=this.ensureWorkspace();return ft({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async refreshEmbeddings(e={}){let t=this.ensureWorkspace();return xn({...e,dbPath:t.knowledgeDbPath,config:this.config()})}providerStatus(e=process.env){return $t(this.config(),e)}modelRegistry(){return rt(this.config())}embeddingStatus(){let e=this.ensureWorkspace();return Yt(e.knowledgeDbPath)}async indexEmbeddings(e={}){let t=this.ensureWorkspace();return Te({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async semanticSearch(e){let t=this.ensureWorkspace();return Re({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async search(e){let t=this.ensureWorkspace();return Ae({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async retrieveContext(e){let t=this.ensureWorkspace();return Le({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async runPrompt(e){let t=this.ensureWorkspace();return un({...e,dbPath:t.knowledgeDbPath,config:this.config()})}async webSearch(e){let t=this.ensureWorkspace();return Pn({...e,dbPath:t.knowledgeDbPath,config:this.config(),safetyPolicy:this.safetyPolicy()})}}function Vn(e={}){return new Yn(e)}import{basename as go}from"path";var pe={name:"@hasna/knowledge",version:"0.2.26",description:"Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",type:"module",bin:{knowledge:"bin/open-knowledge.js","open-knowledge":"bin/open-knowledge.js","open-knowledge-mcp":"bin/open-knowledge-mcp.js"},files:["bin","src","docs","LICENSE","README.md"],scripts:{test:"bun test","test:cli":"bun test tests/cli.test.ts",build:"bun build --target=bun --outfile=bin/open-knowledge.js --minify --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers --external ai --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/deepseek src/cli.ts && bun build --target=bun --outfile=bin/open-knowledge-mcp.js --external @modelcontextprotocol/sdk --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers --external ai --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/deepseek src/mcp.js",prepublishOnly:"bun run build",postinstall:"bun run build"},keywords:["knowledge","cli","agents","json","notes","local","store"],license:"Apache-2.0",publishConfig:{registry:"https://registry.npmjs.org",access:"public"},repository:{type:"git",url:"git+https://github.com/hasna/knowledge.git"},bugs:{url:"https://github.com/hasna/knowledge/issues"},author:"Hasna Inc. <hasna@example.com>",engines:{bun:">=1.0",node:">=18"},dependencies:{"@aws-sdk/client-s3":"^3.1063.0","@aws-sdk/credential-providers":"^3.1063.0","@ai-sdk/anthropic":"^3.0.81","@ai-sdk/deepseek":"^2.0.35","@ai-sdk/openai":"^3.0.68","@modelcontextprotocol/sdk":"^1.29.0",ai:"^6.0.197",zod:"^4.3.6"},devDependencies:{"@types/bun":"^1.3.14"}};var Qn={debug:0,info:1,warn:2,error:3},po=()=>{if(process.env.DEBUG)return"debug";if(process.env.LOG_LEVEL==="debug")return"debug";if(process.env.LOG_LEVEL==="warn")return"warn";if(process.env.LOG_LEVEL==="error")return"error";return"info"};function ee(e,t,n){if(Qn[e]<Qn[po()])return;let r={debug:"[DEBUG]",info:"[INFO]",warn:"[WARN]",error:"[ERROR]"}[e],i=n?`${r} ${t} ${JSON.stringify(n)}`:`${r} ${t}`;if(e==="error")console.error(i);else console.error(i)}var Zn=["add","list","get","delete","update","archive","restore","upsert","untag","export","prune","dedupe","stats","paths","setup","auth","remote","storage","db","wiki","source","ingest","reindex","search","web","ask","build","embeddings","providers","safety","help"],er={ls:"list",rm:"delete",edit:"update",unarchive:"restore",knowledge:"ask"};function mo(e){let t=[],n={};for(let r=0;r<e.length;r+=1){let i=e[r];if(!i.startsWith("-")){t.push(i);continue}switch(i){case"--json":n.json=!0;break;case"--yes":case"-y":n.yes=!0;break;case"--help":case"-h":n.help=!0;break;case"--version":case"-v":n.version=!0;break;case"--desc":n.desc=!0;break;case"--page":case"-p":n.page=Number(e[r+1]),r+=1;break;case"--limit":case"-l":n.limit=Number(e[r+1]),r+=1;break;case"--search":case"-s":n.search=e[r+1],r+=1;break;case"--sort":n.sort=e[r+1],r+=1;break;case"--id":n.id=e[r+1],r+=1;break;case"--store":n.store=e[r+1],r+=1;break;case"--title":n.title=e[r+1],r+=1;break;case"--content":n.content=e[r+1],r+=1;break;case"--url":n.url=e[r+1],r+=1;break;case"--tag":case"-t":n.tag=e[r+1],r+=1;break;case"--format":n.format=e[r+1],r+=1;break;case"--completions":n.completions=e[r+1],r+=1;break;case"--purpose":n.purpose=e[r+1],r+=1;break;case"--model":n.model=e[r+1],r+=1;break;case"--dimensions":n.dimensions=Number(e[r+1]),r+=1;break;case"--semantic":n.semantic=!0;break;case"--context":n.context=!0;break;case"--generate":n.generate=!0;break;case"--approve-write":n.approveWrite=!0;break;case"--provider":n.provider=e[r+1],r+=1;break;case"--mode":n.mode=e[r+1],r+=1;break;case"--api-url":n.apiUrl=e[r+1],r+=1;break;case"--api-key":n.apiKey=e[r+1],r+=1;break;case"--email":n.email=e[r+1],r+=1;break;case"--org":n.org=e[r+1],r+=1;break;case"--org-id":n.orgId=e[r+1],r+=1;break;case"--user-id":n.userId=e[r+1],r+=1;break;case"--domain":n.domain=[...n.domain??[],e[r+1]],r+=1;break;case"--file-results":n.fileResults=!0;break;case"--full":n.full=!0;break;case"--fake":n.fake=!0;break;case"--no-color":n.noColor=!0;break;case"--scope":n.scope=e[r+1],r+=1;break;case"--older-than":n.olderThan=Number(e[r+1]),r+=1;break;case"--empty":n.empty=!0;break;case"--archived":n.archived=!0;break;case"--include-archived":n.includeArchived=!0;break;default:throw Error(`Unknown flag: ${i}. Run 'open-knowledge --help' for valid options.`)}}return{positional:t,flags:n}}function ho(e){if(!e)return"";return er[e]??e}function Eo(e,t){let n=Array.from({length:e.length+1},()=>Array(t.length+1).fill(0));for(let r=0;r<=e.length;r+=1)n[r][0]=r;for(let r=0;r<=t.length;r+=1)n[0][r]=r;for(let r=1;r<=e.length;r+=1)for(let i=1;i<=t.length;i+=1){let s=e[r-1]===t[i-1]?0:1;n[r][i]=Math.min(n[r-1][i]+1,n[r][i-1]+1,n[r-1][i-1]+s)}return n[e.length][t.length]}function ko(e){if(!e)return"";let t=[...Zn,...Object.keys(er)],n="",r=Number.POSITIVE_INFINITY;for(let i of t){let s=Eo(e,i);if(s<r)r=s,n=i}return r<=3?n:""}function yo(){return go(process.argv[1]??"")==="knowledge"}function bo(){console.log(`open-knowledge - local agent knowledge store
|
|
626
626
|
|
|
627
627
|
Usage:
|
|
628
628
|
open-knowledge <command> [options]
|
|
@@ -43,6 +43,13 @@ The future hosted/SaaS wrapper owns:
|
|
|
43
43
|
The OSS package must stay useful without a hosted account. Hosted mode should be
|
|
44
44
|
an optional remote client over explicit API contracts.
|
|
45
45
|
|
|
46
|
+
The detailed hosted boundary is specified in
|
|
47
|
+
[`hosted-wrapper-responsibilities.md`](./hosted-wrapper-responsibilities.md).
|
|
48
|
+
That document is the source of truth for responsibilities that must stay out of
|
|
49
|
+
the OSS package, including tenants, ACL enforcement, connector credentials,
|
|
50
|
+
bucket provisioning, secrets, queues, billing, admin controls, observability, and
|
|
51
|
+
the hosted web UI.
|
|
52
|
+
|
|
46
53
|
The local hosted-aware contract follows the `open-skills` pattern: `mode` is
|
|
47
54
|
`local` by default, `setup --mode hosted` records `hosted.api_url`, env vars
|
|
48
55
|
`KNOWLEDGE_API_URL` and `KNOWLEDGE_API_KEY` can override local config, and
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Hosted Wrapper Responsibilities
|
|
2
|
+
|
|
3
|
+
`open-knowledge` stays local-first and open source. A future hosted/SaaS wrapper
|
|
4
|
+
can implement remote APIs around the same contracts, but tenant state,
|
|
5
|
+
infrastructure ownership, and commercial controls must live outside this
|
|
6
|
+
package.
|
|
7
|
+
|
|
8
|
+
## Boundary
|
|
9
|
+
|
|
10
|
+
The OSS package owns:
|
|
11
|
+
|
|
12
|
+
- Local CLI and MCP behavior.
|
|
13
|
+
- `.hasna/apps/knowledge` workspaces.
|
|
14
|
+
- Local SQLite schema, local artifact catalog, generated wiki artifacts, local
|
|
15
|
+
vector rows, run ledgers, and BYOK provider calls.
|
|
16
|
+
- Source refs, derived chunks, citations, provenance, and read-only resolver
|
|
17
|
+
contracts.
|
|
18
|
+
- Remote client shapes for `registry`, `search`, `ask`, `build`, `sync`,
|
|
19
|
+
`run_status`, `run_logs`, and `run_artifacts`.
|
|
20
|
+
|
|
21
|
+
The hosted wrapper owns:
|
|
22
|
+
|
|
23
|
+
- Tenants, users, orgs, projects, memberships, roles, invitations, sessions, API
|
|
24
|
+
keys, and service accounts.
|
|
25
|
+
- Per-tenant permission checks before retrieval, generation, web search,
|
|
26
|
+
artifact reads, artifact writes, and source sync.
|
|
27
|
+
- Hosted databases, object storage, queue infrastructure, workers, secret
|
|
28
|
+
storage, web UI, billing, rate limits, moderation, observability, and admin
|
|
29
|
+
operations.
|
|
30
|
+
|
|
31
|
+
Local mode must not require hosted identity, billing, queue workers, or hosted
|
|
32
|
+
object storage. Hosted mode is an explicit remote boundary selected through
|
|
33
|
+
`open-knowledge setup --mode hosted` plus `KNOWLEDGE_API_URL` and
|
|
34
|
+
`KNOWLEDGE_API_KEY`.
|
|
35
|
+
|
|
36
|
+
## Identity And Access
|
|
37
|
+
|
|
38
|
+
The hosted wrapper should model:
|
|
39
|
+
|
|
40
|
+
- `users`: human accounts, verified emails, auth identities, MFA state, disabled
|
|
41
|
+
state, and profile metadata.
|
|
42
|
+
- `orgs`: billing/legal tenants, org slugs, data residency settings, default
|
|
43
|
+
retention policies, and owner/admin contacts.
|
|
44
|
+
- `projects`: knowledge workspaces under an org, project slugs, connected
|
|
45
|
+
source scopes, default model/search settings, and artifact storage policy.
|
|
46
|
+
- `memberships`: user to org/project role assignments.
|
|
47
|
+
- `api_keys`: scoped keys for CLI, MCP, CI, workers, and web UI sessions.
|
|
48
|
+
- `service_accounts`: non-human actors for ingestion, sync, indexing, and
|
|
49
|
+
scheduled maintenance.
|
|
50
|
+
|
|
51
|
+
Permission checks must happen before context assembly. The wrapper should never
|
|
52
|
+
retrieve broad context and filter after generation. The retrieval API should
|
|
53
|
+
accept caller identity, project id, requested purpose, source refs, and requested
|
|
54
|
+
operations, then return only authorized source chunks, generated artifacts, and
|
|
55
|
+
citations.
|
|
56
|
+
|
|
57
|
+
## Open-Files Integration
|
|
58
|
+
|
|
59
|
+
`open-files` remains the source of truth for raw source bytes. The hosted wrapper
|
|
60
|
+
should own connector orchestration and source sync:
|
|
61
|
+
|
|
62
|
+
- Google Drive, Slack, GitHub, Notion, S3, local-upload, web crawl, and future
|
|
63
|
+
connector credentials.
|
|
64
|
+
- Connector OAuth flows, refresh tokens, webhook registrations, cursor state,
|
|
65
|
+
backoff, retries, and error recovery.
|
|
66
|
+
- Source snapshots, immutable object ids, revisions, hashes, MIME metadata,
|
|
67
|
+
extracted text refs, ACL metadata, and change outboxes.
|
|
68
|
+
- Read-only resolver APIs that expose derived extracted text or allowed chunks
|
|
69
|
+
to `open-knowledge` without exposing storage credentials.
|
|
70
|
+
|
|
71
|
+
`open-knowledge` should continue to consume manifests, source refs, and outbox
|
|
72
|
+
events. It should not own connector credentials or raw source object lifecycle.
|
|
73
|
+
|
|
74
|
+
## Storage And Secrets
|
|
75
|
+
|
|
76
|
+
The hosted wrapper should provision and enforce:
|
|
77
|
+
|
|
78
|
+
- Per-org/project generated-artifact prefixes in S3 or another object store.
|
|
79
|
+
- Bucket policies, KMS keys, retention rules, lifecycle rules, replication, and
|
|
80
|
+
object lock settings where required.
|
|
81
|
+
- Secrets for provider keys, connector keys, AWS roles, RDS URLs, webhook
|
|
82
|
+
secrets, and encryption material.
|
|
83
|
+
- Migration aliases from legacy bucket and secret names to canonical names.
|
|
84
|
+
|
|
85
|
+
The OSS package may know a storage contract, bucket name, prefix, region, and
|
|
86
|
+
profile. It must not contain tenant secret values, connector credentials, RDS
|
|
87
|
+
passwords, hosted KMS key material, or privileged AWS role assumptions.
|
|
88
|
+
|
|
89
|
+
Generated artifacts are safe to sync remotely only when they remain derived:
|
|
90
|
+
wiki pages, index shards, schema files, logs, exports, run payloads, embeddings,
|
|
91
|
+
and citation metadata. Raw source bytes stay in `open-files`.
|
|
92
|
+
|
|
93
|
+
## Remote Jobs
|
|
94
|
+
|
|
95
|
+
The wrapper should implement durable jobs for:
|
|
96
|
+
|
|
97
|
+
- Manifest import and source sync from `open-files`.
|
|
98
|
+
- Extraction and redaction refresh.
|
|
99
|
+
- Embedding index refresh and stale-revision invalidation.
|
|
100
|
+
- Provider-native web search with audit logs and source capture.
|
|
101
|
+
- Wiki compile, answer filing, lint, and maintenance runs.
|
|
102
|
+
- Artifact sync between local clients and hosted storage.
|
|
103
|
+
- Remote search, ask, and build workflows.
|
|
104
|
+
|
|
105
|
+
Jobs should expose the same run contract as the local package: status, prompt or
|
|
106
|
+
query, provider/model, usage, citations, artifacts, events, logs, errors,
|
|
107
|
+
timestamps, and cost metadata. Workers should be idempotent and keyed by source
|
|
108
|
+
revision, artifact hash, or run id where possible.
|
|
109
|
+
|
|
110
|
+
## API Surface
|
|
111
|
+
|
|
112
|
+
The wrapper should implement the contract printed by:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
open-knowledge remote contracts --scope project --json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Required endpoint families:
|
|
119
|
+
|
|
120
|
+
- `registry`: service capabilities, contract version, source contract, artifact
|
|
121
|
+
contract, and limits.
|
|
122
|
+
- `search`: permission-aware hybrid retrieval.
|
|
123
|
+
- `ask`: retrieval plus answer generation.
|
|
124
|
+
- `build`: retrieval plus generated artifact proposals or approved writes.
|
|
125
|
+
- `sync`: source/artifact synchronization.
|
|
126
|
+
- `runs`: status, logs, events, usage, and artifacts.
|
|
127
|
+
- `artifacts`: generated artifact reads and writes with policy checks.
|
|
128
|
+
|
|
129
|
+
The web UI should use the same API surface as CLI/MCP clients. It should not
|
|
130
|
+
gain hidden write paths that bypass approval gates, source permission checks, or
|
|
131
|
+
run ledgers.
|
|
132
|
+
|
|
133
|
+
## Billing, Limits, And Abuse Controls
|
|
134
|
+
|
|
135
|
+
The hosted wrapper owns:
|
|
136
|
+
|
|
137
|
+
- Plan limits for seats, projects, sources, storage, embeddings, web search,
|
|
138
|
+
provider tokens, and scheduled jobs.
|
|
139
|
+
- Per-user, per-org, per-project, and per-key rate limits.
|
|
140
|
+
- Provider cost attribution and billing events.
|
|
141
|
+
- Abuse detection for web search, provider calls, bulk exports, connector syncs,
|
|
142
|
+
and artifact downloads.
|
|
143
|
+
- Admin controls to pause projects, rotate keys, disable connectors, revoke API
|
|
144
|
+
keys, and quarantine generated artifacts.
|
|
145
|
+
|
|
146
|
+
Local OSS mode may report provider usage, but it must not require billing.
|
|
147
|
+
|
|
148
|
+
## Audit And Observability
|
|
149
|
+
|
|
150
|
+
The hosted wrapper should record:
|
|
151
|
+
|
|
152
|
+
- Authentication and API-key events.
|
|
153
|
+
- Source connector reads, source resolver reads, and source permission denials.
|
|
154
|
+
- Search, ask, build, compile, lint, web-search, sync, and export runs.
|
|
155
|
+
- Generated artifact writes, approvals, rejections, and rollbacks.
|
|
156
|
+
- Admin and moderation actions.
|
|
157
|
+
- Worker retries, dead-letter events, queue latency, token usage, cost, and
|
|
158
|
+
storage growth.
|
|
159
|
+
|
|
160
|
+
Local run ledgers and audit events remain useful for offline workflows. Hosted
|
|
161
|
+
audit logs should add tenant identity, actor identity, IP/user-agent metadata,
|
|
162
|
+
request ids, and retention controls.
|
|
163
|
+
|
|
164
|
+
## Non-Goals For OSS
|
|
165
|
+
|
|
166
|
+
The OSS package should not implement:
|
|
167
|
+
|
|
168
|
+
- Hosted user/org/project management.
|
|
169
|
+
- Billing, plans, commercial rate limits, or invoices.
|
|
170
|
+
- Hosted connector OAuth flows or connector credential storage.
|
|
171
|
+
- Shared multi-tenant ACL enforcement.
|
|
172
|
+
- Hosted queue workers, RDS provisioning, bucket provisioning, KMS management,
|
|
173
|
+
or admin/moderation workflows.
|
|
174
|
+
- A privileged web UI write path.
|
|
175
|
+
|
|
176
|
+
The OSS package can expose the contracts and local behavior needed to make those
|
|
177
|
+
systems straightforward to build on top.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Company Wiki Workflow
|
|
2
|
+
|
|
3
|
+
This workflow shows how to use `open-knowledge` as an AI-native company wiki
|
|
4
|
+
layer over source files owned by `open-files`.
|
|
5
|
+
|
|
6
|
+
## 1. Initialize The Project Workspace
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
open-knowledge paths --scope project --json
|
|
10
|
+
open-knowledge db init --scope project --json
|
|
11
|
+
open-knowledge wiki init --scope project --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Project state is created under:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
.hasna/apps/knowledge/
|
|
18
|
+
config.json
|
|
19
|
+
knowledge.db
|
|
20
|
+
artifacts/
|
|
21
|
+
indexes/
|
|
22
|
+
logs/
|
|
23
|
+
runs/
|
|
24
|
+
schemas/
|
|
25
|
+
wiki/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 2. Import Source Metadata From Open-Files
|
|
29
|
+
|
|
30
|
+
Use an `open-files` manifest with source refs, revisions, permissions, hashes,
|
|
31
|
+
and extracted text:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"source_ref": "open-files://file/file_handbook/revision/rev_20260608",
|
|
36
|
+
"file_id": "file_handbook",
|
|
37
|
+
"path": "Handbook/Policy.md",
|
|
38
|
+
"name": "Policy.md",
|
|
39
|
+
"mime": "text/markdown",
|
|
40
|
+
"hash": "sha256:...",
|
|
41
|
+
"status": "active",
|
|
42
|
+
"permissions": {
|
|
43
|
+
"mode": "read_only",
|
|
44
|
+
"allowed_purposes": ["knowledge_answer", "knowledge_index"]
|
|
45
|
+
},
|
|
46
|
+
"extracted_text": "Policy text..."
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then ingest it:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
open-knowledge ingest manifest ./open-files-manifest.jsonl --scope project --json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The knowledge app stores source refs, revisions, redacted chunks, offsets, and
|
|
57
|
+
citations. It does not store raw source bytes or connector credentials.
|
|
58
|
+
|
|
59
|
+
## 3. Search And Build Context
|
|
60
|
+
|
|
61
|
+
Run local keyword/catalog search first:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
open-knowledge search "expense policy" --scope project --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Add deterministic local semantic indexing for a smoke test:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
open-knowledge embeddings index --scope project --fake --dimensions 8 --json
|
|
71
|
+
open-knowledge search "expense policy" --scope project --semantic --fake --dimensions 8 --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Ask for an agent-ready context pack:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
open-knowledge search "expense policy" --scope project --context --json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 4. Answer With Citations
|
|
81
|
+
|
|
82
|
+
Create a local citation draft:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
knowledge "How do we approve expenses?" --scope project --json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use provider generation explicitly:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
OPENAI_API_KEY=... \
|
|
92
|
+
knowledge "How do we approve expenses?" \
|
|
93
|
+
--scope project \
|
|
94
|
+
--generate \
|
|
95
|
+
--model openai:gpt-5-mini \
|
|
96
|
+
--json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every prompt run records `runs`, `run_events`, provider/model metadata, usage,
|
|
100
|
+
citations, and proposed wiki updates.
|
|
101
|
+
|
|
102
|
+
## 5. Compile Durable Wiki Pages
|
|
103
|
+
|
|
104
|
+
Compile a cited page from indexed chunks:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
open-knowledge wiki compile "expense policy" \
|
|
108
|
+
--title "Expense Policy" \
|
|
109
|
+
--scope project \
|
|
110
|
+
--json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
File an approved answer note:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
open-knowledge wiki file-answer "How do we approve expenses?" \
|
|
117
|
+
--content "Use manager approval and cite the policy source." \
|
|
118
|
+
--approve-write \
|
|
119
|
+
--scope project \
|
|
120
|
+
--json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Lint the generated wiki:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
open-knowledge wiki lint --scope project --json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Generated pages are written through the artifact store and cataloged in
|
|
130
|
+
`knowledge.db`; index rows and logs are sharded rather than stored in one large
|
|
131
|
+
Markdown file.
|
|
132
|
+
|
|
133
|
+
## 6. Keep Sources Fresh
|
|
134
|
+
|
|
135
|
+
Consume open-files outbox events after source changes:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
open-knowledge reindex outbox ./open-files-outbox.jsonl --scope project --json
|
|
139
|
+
open-knowledge reindex enqueue --scope project --json
|
|
140
|
+
open-knowledge reindex embeddings --scope project --fake --dimensions 8 --json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This invalidates stale source chunks and refreshes embeddings without losing the
|
|
144
|
+
source refs and citation provenance.
|
|
145
|
+
|
|
146
|
+
## 7. Expose The Wiki To Agents Through MCP
|
|
147
|
+
|
|
148
|
+
Run MCP over stdio:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
open-knowledge-mcp
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or run local Streamable HTTP:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
open-knowledge-mcp --http --port 8819
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Agents should prefer stable tools such as `knowledge_search`, `knowledge_ask`,
|
|
161
|
+
`knowledge_build`, `knowledge_get`, `knowledge_lint`, and
|
|
162
|
+
`knowledge_run_status`. They can inspect project resources such as
|
|
163
|
+
`knowledge://project/wiki/pages`, `knowledge://project/runs`, and
|
|
164
|
+
`knowledge://project/open-files`.
|
|
165
|
+
|
|
166
|
+
## 8. Optional Hosted And S3 Mode
|
|
167
|
+
|
|
168
|
+
Hosted mode is only a remote client boundary:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
open-knowledge setup --mode hosted --api-url https://knowledge.hasna.xyz --scope project --json
|
|
172
|
+
open-knowledge remote contracts --scope project --json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Generated artifacts may use S3 when configured, but raw source files still stay
|
|
176
|
+
in `open-files`.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# JSON To SQLite Migration
|
|
2
|
+
|
|
3
|
+
`open-knowledge` began as a simple JSON note store. Current project mode uses a
|
|
4
|
+
Hasna app workspace and a versioned SQLite catalog:
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
.hasna/apps/knowledge/
|
|
8
|
+
db.json
|
|
9
|
+
knowledge.db
|
|
10
|
+
artifacts/
|
|
11
|
+
indexes/
|
|
12
|
+
logs/
|
|
13
|
+
runs/
|
|
14
|
+
schemas/
|
|
15
|
+
wiki/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The JSON store remains available for compatibility with note commands such as
|
|
19
|
+
`add`, `list`, `get`, `update`, `delete`, and `export`. The SQLite catalog is
|
|
20
|
+
used for source refs, source revisions, chunks, citations, embeddings, wiki
|
|
21
|
+
pages, generated artifacts, runs, audit events, and reindex jobs.
|
|
22
|
+
|
|
23
|
+
## What Migrates Automatically
|
|
24
|
+
|
|
25
|
+
Global legacy notes are migrated on first use:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
~/.open-knowledge/db.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
to:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
~/.hasna/apps/knowledge/db.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This happens only when the new Hasna JSON store does not already exist. The
|
|
38
|
+
legacy file is not deleted.
|
|
39
|
+
|
|
40
|
+
Project mode writes directly to:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
<project>/.hasna/apps/knowledge/db.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
when compatibility note commands are used with `--scope project`.
|
|
47
|
+
|
|
48
|
+
## What Requires Explicit Ingestion
|
|
49
|
+
|
|
50
|
+
SQLite knowledge records are not inferred from old JSON notes automatically.
|
|
51
|
+
Use explicit commands so provenance, permissions, citations, and redaction are
|
|
52
|
+
recorded correctly.
|
|
53
|
+
|
|
54
|
+
Initialize the project catalog:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
open-knowledge db init --scope project --json
|
|
58
|
+
open-knowledge wiki init --scope project --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Import open-files manifests:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
open-knowledge ingest manifest ./open-files-manifest.jsonl --scope project --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Import one allowed source ref:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
open-knowledge ingest source file:///absolute/path/to/handbook.md \
|
|
71
|
+
--purpose knowledge_index \
|
|
72
|
+
--scope project \
|
|
73
|
+
--json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Resolve indexed source evidence:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
open-knowledge source resolve open-files://file/file_123/revision/rev_456 \
|
|
80
|
+
--purpose knowledge_answer \
|
|
81
|
+
--scope project \
|
|
82
|
+
--json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Recommended Migration Path
|
|
86
|
+
|
|
87
|
+
1. Keep the legacy JSON note store as an exportable compatibility layer.
|
|
88
|
+
2. Run `open-knowledge paths --scope project --json` and confirm the project
|
|
89
|
+
workspace is `.hasna/apps/knowledge`.
|
|
90
|
+
3. Initialize `knowledge.db` with `open-knowledge db init --scope project`.
|
|
91
|
+
4. Ingest source manifests from `open-files` rather than copying raw files into
|
|
92
|
+
`open-knowledge`.
|
|
93
|
+
5. Run `open-knowledge search --scope project --json` to verify source chunks.
|
|
94
|
+
6. Run `open-knowledge wiki compile` for durable cited pages.
|
|
95
|
+
7. Run `open-knowledge wiki lint --scope project --json` before treating pages
|
|
96
|
+
as company knowledge.
|
|
97
|
+
8. Use `open-knowledge export --format jsonl` if legacy notes need to be
|
|
98
|
+
archived or transformed outside the app.
|
|
99
|
+
|
|
100
|
+
## JSON Output Contracts
|
|
101
|
+
|
|
102
|
+
Use `--json` during migration. Commands return stable objects with `ok: true`
|
|
103
|
+
when successful and command-specific fields such as:
|
|
104
|
+
|
|
105
|
+
- `paths`: workspace paths and config.
|
|
106
|
+
- `db stats`: schema version and table counts.
|
|
107
|
+
- `ingest manifest`: sources, revisions, chunks, redactions, and skipped rows.
|
|
108
|
+
- `source resolve`: read-only source metadata, chunks, citations, and evidence.
|
|
109
|
+
- `search --context`: excerpts, citations, graph evidence, and warnings.
|
|
110
|
+
- `ask|build`: run id, answer, context, citations, proposed wiki updates, write
|
|
111
|
+
policy, usage, and warnings.
|
|
112
|
+
- `wiki compile`: page id, artifact URI, citations written, index updates, and
|
|
113
|
+
log shard key.
|
|
114
|
+
|
|
115
|
+
## Safety Rules During Migration
|
|
116
|
+
|
|
117
|
+
- Prefer `open-files://` refs for durable company sources.
|
|
118
|
+
- Keep raw source bytes in `open-files`; do not import them as generated wiki
|
|
119
|
+
artifacts.
|
|
120
|
+
- Enable S3 reads only for allowed buckets:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
HASNA_KNOWLEDGE_ALLOW_S3_READS=1 \
|
|
124
|
+
HASNA_KNOWLEDGE_ALLOWED_S3_BUCKETS=my-bucket \
|
|
125
|
+
open-knowledge ingest manifest s3://my-bucket/path/manifest.jsonl \
|
|
126
|
+
--scope project \
|
|
127
|
+
--json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- Enable web search only when current external context is required:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
HASNA_KNOWLEDGE_WEB_SEARCH=1 \
|
|
134
|
+
open-knowledge web search "current policy source" --provider openai --json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- Use `--approve-write` only when a generated wiki artifact should be durable.
|
|
138
|
+
|
|
139
|
+
## Hosted Migration
|
|
140
|
+
|
|
141
|
+
Hosted mode should not change local migration semantics. It only records a
|
|
142
|
+
remote API boundary:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
open-knowledge setup --mode hosted --api-url https://knowledge.hasna.xyz --scope project --json
|
|
146
|
+
open-knowledge remote contracts --scope project --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A SaaS wrapper can later sync generated artifacts, run jobs, enforce tenant ACLs,
|
|
150
|
+
and store artifacts in S3, but the local package remains usable without a hosted
|
|
151
|
+
account.
|