@hasna/knowledge 0.2.2 → 0.2.3

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import{mkdirSync as c,readFileSync as L,writeFileSync as u,existsSync as k,renameSync as m,unlinkSync as b}from"fs";import{dirname as f}from"path";import{homedir as i}from"os";import{randomUUID as P}from"crypto";function v(){return`${i()}/.open-knowledge/db.json`}function x(B){if(!k(B))c(f(B),{recursive:!0}),u(B,JSON.stringify({items:[]},null,2))}function d(B){return`${B}.lock`}function g(B,W){let Q=Date.now();while(Date.now()-Q<5000){try{if(!k(B)){u(B,JSON.stringify({owner:W,ts:Date.now()}));return}let C=JSON.parse(L(B,"utf8"));if(Date.now()-C.ts>1e4)b(B)}catch{}let J=Date.now();while(Date.now()-J<50);}throw Error(`Could not acquire lock on ${B} after 5000ms`)}function l(B,W){try{if(k(B)){if(JSON.parse(L(B,"utf8")).owner===W)b(B)}}catch{}}function G(B){x(B);let W=L(B,"utf8"),z=JSON.parse(W);if(!z||!Array.isArray(z.items))return{items:[]};return z}function M(B,W){let z=`${B}.tmp.${P()}`;u(z,JSON.stringify(W,null,2)),m(z,B)}function O(B,W){let z=P(),K=d(B);g(K,z);try{return W()}finally{l(K,z)}}function p(){return`k_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`}var A={name:"@hasna/knowledge",version:"0.2.2",description:"Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",type:"module",bin:{"open-knowledge":"./src/cli.ts","open-knowledge-mcp":"./src/mcp.js"},scripts:{test:"bun test","test:cli":"bun test tests/cli.test.ts",build:"bun build --target=bun --outfile=bin/open-knowledge.js --minify src/cli.ts",prepublishOnly:"bun run build",postinstall:"bun run build"},keywords:["knowledge","cli","agents","json","notes","local","store"],license:"Apache-2.0",repository:{type:"git",url:"https://github.com/hasna/knowledge"},bugs:{url:"https://github.com/hasna/knowledge/issues"},author:"Hasna Inc. <hasna@example.com>",engines:{bun:">=1.0",node:">=18"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.3.6"}};var h={debug:0,info:1,warn:2,error:3},r=()=>{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 D(B,W,z){if(h[B]<h[r()])return;let K={debug:"[DEBUG]",info:"[INFO]",warn:"[WARN]",error:"[ERROR]"}[B],Q=z?`${K} ${W} ${JSON.stringify(z)}`:`${K} ${W}`;if(B==="error")console.error(Q);else console.error(Q)}var s=["add","list","get","delete","update","export","prune","dedupe","stats","help"],n={ls:"list",rm:"delete",edit:"update"};function o(B){let W=[],z={};for(let K=0;K<B.length;K+=1){let Q=B[K];if(!Q.startsWith("-")){W.push(Q);continue}switch(Q){case"--json":z.json=!0;break;case"--yes":case"-y":z.yes=!0;break;case"--help":case"-h":z.help=!0;break;case"--version":case"-v":z.version=!0;break;case"--desc":z.desc=!0;break;case"--page":case"-p":z.page=Number(B[K+1]),K+=1;break;case"--limit":case"-l":z.limit=Number(B[K+1]),K+=1;break;case"--search":case"-s":z.search=B[K+1],K+=1;break;case"--sort":z.sort=B[K+1],K+=1;break;case"--id":z.id=B[K+1],K+=1;break;case"--store":z.store=B[K+1],K+=1;break;case"--title":z.title=B[K+1],K+=1;break;case"--content":z.content=B[K+1],K+=1;break;case"--url":z.url=B[K+1],K+=1;break;case"--tag":case"-t":z.tag=B[K+1],K+=1;break;case"--format":z.format=B[K+1],K+=1;break;case"--completions":z.completions=B[K+1],K+=1;break;case"--no-color":z.noColor=!0;break;case"--scope":z.scope=B[K+1],K+=1;break;case"--older-than":z.olderThan=Number(B[K+1]),K+=1;break;case"--empty":z.empty=!0;break;default:throw Error(`Unknown flag: ${Q}. Run 'open-knowledge --help' for valid options.`)}}return{positional:W,flags:z}}function t(B){if(!B)return"";return n[B]??B}function a(B,W){let z=Array.from({length:B.length+1},()=>Array(W.length+1).fill(0));for(let K=0;K<=B.length;K+=1)z[K][0]=K;for(let K=0;K<=W.length;K+=1)z[0][K]=K;for(let K=1;K<=B.length;K+=1)for(let Q=1;Q<=W.length;Q+=1){let J=B[K-1]===W[Q-1]?0:1;z[K][Q]=Math.min(z[K-1][Q]+1,z[K][Q-1]+1,z[K-1][Q-1]+J)}return z[B.length][W.length]}function z0(B){if(!B)return"";let W=[...s,...Object.keys(n)],z="",K=Number.POSITIVE_INFINITY;for(let Q of W){let J=a(B,Q);if(J<K)K=J,z=Q}return K<=3?z:""}function B0(){console.log(`open-knowledge - local agent knowledge store
3
+ import{mkdirSync as c,readFileSync as L,writeFileSync as u,existsSync as k,renameSync as m,unlinkSync as S}from"fs";import{dirname as f}from"path";import{homedir as i}from"os";import{randomUUID as P}from"crypto";function p(){return`${i()}/.open-knowledge/db.json`}function b(R){if(!k(R))c(f(R),{recursive:!0}),u(R,JSON.stringify({items:[]},null,2))}function d(R){return`${R}.lock`}function g(R,W){let K=Date.now();while(Date.now()-K<5000){try{if(!k(R)){u(R,JSON.stringify({owner:W,ts:Date.now()}));return}let U=JSON.parse(L(R,"utf8"));if(Date.now()-U.ts>1e4)S(R)}catch{}let N=Date.now();while(Date.now()-N<50);}throw Error(`Could not acquire lock on ${R} after 5000ms`)}function l(R,W){try{if(k(R)){if(JSON.parse(L(R,"utf8")).owner===W)S(R)}}catch{}}function V(R){b(R);let W=L(R,"utf8"),z=JSON.parse(W);if(!z||!Array.isArray(z.items))return{items:[]};return z}function j(R,W){let z=`${R}.tmp.${P()}`;u(z,JSON.stringify(W,null,2)),m(z,R)}function G(R,W){let z=P(),B=d(R);g(B,z);try{return W()}finally{l(B,z)}}function v(){return`k_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`}var F={name:"@hasna/knowledge",version:"0.2.3",description:"Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",type:"module",bin:{"open-knowledge":"./bin/open-knowledge.js","open-knowledge-mcp":"./bin/open-knowledge-mcp.js"},files:["bin","src/mcp-http.js","src/store.ts","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 src/cli.ts && bun build --target=bun --outfile=bin/open-knowledge-mcp.js --external @modelcontextprotocol/sdk 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:"https://github.com/hasna/knowledge"},bugs:{url:"https://github.com/hasna/knowledge/issues"},author:"Hasna Inc. <hasna@example.com>",engines:{bun:">=1.0",node:">=18"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.3.6"}};var n={debug:0,info:1,warn:2,error:3},s=()=>{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 T(R,W,z){if(n[R]<n[s()])return;let B={debug:"[DEBUG]",info:"[INFO]",warn:"[WARN]",error:"[ERROR]"}[R],K=z?`${B} ${W} ${JSON.stringify(z)}`:`${B} ${W}`;if(R==="error")console.error(K);else console.error(K)}var r=["add","list","get","delete","update","export","prune","dedupe","stats","help"],h={ls:"list",rm:"delete",edit:"update"};function o(R){let W=[],z={};for(let B=0;B<R.length;B+=1){let K=R[B];if(!K.startsWith("-")){W.push(K);continue}switch(K){case"--json":z.json=!0;break;case"--yes":case"-y":z.yes=!0;break;case"--help":case"-h":z.help=!0;break;case"--version":case"-v":z.version=!0;break;case"--desc":z.desc=!0;break;case"--page":case"-p":z.page=Number(R[B+1]),B+=1;break;case"--limit":case"-l":z.limit=Number(R[B+1]),B+=1;break;case"--search":case"-s":z.search=R[B+1],B+=1;break;case"--sort":z.sort=R[B+1],B+=1;break;case"--id":z.id=R[B+1],B+=1;break;case"--store":z.store=R[B+1],B+=1;break;case"--title":z.title=R[B+1],B+=1;break;case"--content":z.content=R[B+1],B+=1;break;case"--url":z.url=R[B+1],B+=1;break;case"--tag":case"-t":z.tag=R[B+1],B+=1;break;case"--format":z.format=R[B+1],B+=1;break;case"--completions":z.completions=R[B+1],B+=1;break;case"--no-color":z.noColor=!0;break;case"--scope":z.scope=R[B+1],B+=1;break;case"--older-than":z.olderThan=Number(R[B+1]),B+=1;break;case"--empty":z.empty=!0;break;default:throw Error(`Unknown flag: ${K}. Run 'open-knowledge --help' for valid options.`)}}return{positional:W,flags:z}}function t(R){if(!R)return"";return h[R]??R}function a(R,W){let z=Array.from({length:R.length+1},()=>Array(W.length+1).fill(0));for(let B=0;B<=R.length;B+=1)z[B][0]=B;for(let B=0;B<=W.length;B+=1)z[0][B]=B;for(let B=1;B<=R.length;B+=1)for(let K=1;K<=W.length;K+=1){let N=R[B-1]===W[K-1]?0:1;z[B][K]=Math.min(z[B-1][K]+1,z[B][K-1]+1,z[B-1][K-1]+N)}return z[R.length][W.length]}function z0(R){if(!R)return"";let W=[...r,...Object.keys(h)],z="",B=Number.POSITIVE_INFINITY;for(let K of W){let N=a(R,K);if(N<B)B=N,z=K}return B<=3?z:""}function R0(){console.log(`open-knowledge - local agent knowledge store
4
4
 
5
5
  Usage:
6
6
  open-knowledge <command> [options]
@@ -54,5 +54,5 @@ Export Options:
54
54
 
55
55
  Prune Options:
56
56
  --older-than <days> Remove items older than N days
57
- --empty Remove items with empty content`)}function K0(B){if(B==="add"){console.log("Usage: open-knowledge add <title> <content> [--url <url>] [-t <tag>] [--json]");return}if(B==="list"||B==="ls"){console.log("Usage: open-knowledge list|ls [--format table|json] [-p <page>] [-l <limit>] [-s <search>] [-t <tag>] [--sort created|title] [--desc] [--json]");return}if(B==="get"){console.log("Usage: open-knowledge get --id <id> [--json]");return}if(B==="update"||B==="edit"){console.log("Usage: open-knowledge update|edit --id <id> [--title <title>] [--content <content>] [--url <url>] [-t <tag>] [--json]");return}if(B==="delete"||B==="rm"){console.log("Usage: open-knowledge delete|rm --id <id> -y [--json]");return}if(B==="export"){console.log("Usage: open-knowledge export [--format jsonl] [--json]");return}if(B==="prune"){console.log("Usage: open-knowledge prune --yes [--older-than <days>] [--empty] [--json]");return}if(B==="dedupe"){console.log("Usage: open-knowledge dedupe --yes [--json]");return}if(B==="stats"){console.log("Usage: open-knowledge stats [--json]");return}B0()}function Q0(B){if(B.noColor||process.env.NO_COLOR)return!1;if(process.env.FORCE_COLOR)return!0;return process.stdout.isTTY===!0}function q(B,W,z){if(W){console.log(JSON.stringify(B,null,2));return}if(typeof B==="string"){console.log(B);return}console.log(B.message??JSON.stringify(B,null,2))}function S(B){if(!B.id)throw Error("Missing required --id. Example: open-knowledge get --id <id>")}function R0(B,W){let z=W.sort??"created";if(z!=="created"&&z!=="title")throw Error("Invalid --sort value. Use 'created' or 'title'.");let K=[...B].sort((Q,J)=>{if(z==="title")return Q.title.localeCompare(J.title);return Q.created_at.localeCompare(J.created_at)});if(W.desc)K.reverse();return{sorted:K,sort:z,direction:W.desc?"desc":"asc"}}function W0(B){let{positional:W,flags:z}=o(B);if(D("debug","CLI invoked",{command:W[0],flags:{json:z.json,store:z.store}}),z.version){console.log(z.json?JSON.stringify({name:A.name,version:A.version},null,2):A.version);return}if(z.completions){let R=z.completions;if(R==="bash")console.log('_open_knowledge() { local cur; cur="${COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update delete export help ls rm edit --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --no-color --scope" -- "$cur")); }; complete -F _open_knowledge open-knowledge');else if(R==="zsh")console.log(`#compdef open-knowledge
58
- _open_knowledge() { _arguments -C "1: :(add list get update delete export help ls rm edit)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"{created,title}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--no-color)--no-color[disable color]" "(--scope)--scope"{local,global,project}:" }; _open_knowledge`);else if(R==="fish")console.log('complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update delete export help ls rm edit"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"');else throw Error("Invalid --completions value. Use 'bash', 'zsh', or 'fish'.");return}let K=t(W[0]);if(!K||z.help||K==="help"){K0(W[1]);return}let Q=z.store;if(!Q)if(z.scope==="project")Q="./.open-knowledge/db.json";else Q=v();if(x(Q),K==="add"){let R=W[1],Y=W[2];if(!R||!Y)throw Error("Usage: open-knowledge add <title> <content>");O(Q,()=>{let X=G(Q),Z={id:p(),title:R,content:Y,url:z.url??null,tags:z.tag?[z.tag]:[],created_at:new Date().toISOString(),updated_at:new Date().toISOString()};X.items.push(Z),M(Q,X),D("info","Item added",{id:Z.id,title:Z.title}),q({ok:!0,item:Z,message:`Added ${Z.id}`},z.json)});return}if(K==="list"){if(z.format!==void 0&&z.format!=="table"&&z.format!=="json")throw Error("Invalid --format value for list. Use 'table' or 'json'.");O(Q,()=>{let R=G(Q),Y=Number.isFinite(z.page)&&z.page>0?z.page:1,X=Number.isFinite(z.limit)&&z.limit>0?z.limit:20,Z=z.search?String(z.search).toLowerCase():"",N=z.tag?String(z.tag).toLowerCase():"",E=z.format==="table"||!z.json&&!z.format&&Q0(z),U=z.json||z.format==="json",_=R.items;if(Z)_=_.filter(($)=>$.title.toLowerCase().includes(Z)||$.content.toLowerCase().includes(Z));if(N)_=_.filter(($)=>$.tags&&$.tags.map((I)=>I.toLowerCase()).includes(N));let{sorted:H,sort:V,direction:j}=R0(_,z),w=(Y-1)*X,F=H.slice(w,w+X),y=Math.max(1,Math.ceil(H.length/X));if(U){q({ok:!0,page:Y,limit:X,total:H.length,total_pages:y,sort:V,direction:j,items:F},!0);return}if(F.length===0){q(`No items found (search=${Z||"none"}, tag=${N||"none"})`,!1);return}if(E){let $=(T)=>T,I=`${$("ID")} ${$("TITLE")} ${$("CREATED")} ${$("URL")} ${$("TAGS")}`;console.log(I);for(let T of F)console.log(`${T.id} ${$(T.title)} ${T.created_at} ${T.url?$(T.url):""} ${T.tags?.length?$(`[${T.tags.join(", ")}]`):""}`);console.log(`Page ${Y}/${y} | showing ${F.length} of ${H.length} | sort=${V} ${j} | search=${Z||"none"} | tag=${N||"none"}`)}else{for(let $ of F)console.log(`${$.id} ${$.title} ${$.created_at}${$.url?` ${$.url}`:""}${$.tags?.length?` [${$.tags.join(", ")}]`:""}`);console.log(`Page ${Y}/${y} | showing ${F.length} of ${H.length} | sort=${V} ${j} | search=${Z||"none"} | tag=${N||"none"}`)}});return}if(K==="get"){S(z),O(Q,()=>{let Y=G(Q).items.find((X)=>X.id===z.id);if(!Y)throw Error(`Item not found: ${z.id}`);q({ok:!0,item:Y,message:`${Y.id}: ${Y.title}`},z.json)});return}if(K==="update"){S(z),O(Q,()=>{let R=G(Q),Y=R.items.findIndex((Z)=>Z.id===z.id);if(Y===-1)throw Error(`Item not found: ${z.id}`);let X=R.items[Y];if(z.title!==void 0)X.title=z.title;if(z.content!==void 0)X.content=z.content;if(z.url!==void 0)X.url=z.url;if(z.tag!==void 0){if(X.tags=X.tags||[],!X.tags.map((Z)=>Z.toLowerCase()).includes(z.tag.toLowerCase()))X.tags.push(z.tag)}X.updated_at=new Date().toISOString(),R.items[Y]=X,M(Q,R),q({ok:!0,item:X,message:`Updated ${X.id}`},z.json)});return}if(K==="delete"){if(S(z),!z.yes)throw Error("Refusing delete without --yes. Re-run with: open-knowledge delete --id <id> --yes");O(Q,()=>{let R=G(Q),Y=R.items.length;R.items=R.items.filter((Z)=>Z.id!==z.id);let X=Y!==R.items.length;if(M(Q,R),!X)throw Error(`Item not found: ${z.id}`);D("info","Item deleted",{id:z.id}),q({ok:!0,deleted_id:z.id,message:`Deleted ${z.id}`},z.json)});return}if(K==="export"){let R=z.format??"json";if(R!=="json"&&R!=="jsonl")throw Error("Invalid --format. Use 'json' or 'jsonl'.");O(Q,()=>{let Y=G(Q);if(R==="jsonl")for(let X of Y.items)console.log(JSON.stringify(X));else q({ok:!0,items:Y.items},z.json)});return}if(K==="prune"){if(!z.yes)throw Error("Refusing prune without --yes. Re-run with: open-knowledge prune --yes [--older-than <days>] [--empty]");O(Q,()=>{let R=G(Q),Y=R.items.length;if(z.olderThan!==void 0){let Z=new Date;Z.setDate(Z.getDate()-z.olderThan),R.items=R.items.filter((N)=>new Date(N.created_at)>=Z)}if(z.empty)R.items=R.items.filter((Z)=>Z.content.trim().length>0);let X=Y-R.items.length;M(Q,R),D("info","Prune completed",{pruned:X,remaining:R.items.length}),q({ok:!0,pruned:X,remaining:R.items.length,message:`Pruned ${X} item(s)`},z.json)});return}if(K==="dedupe"){if(!z.yes)throw Error("Refusing dedupe without --yes. Re-run with: open-knowledge dedupe --yes [--json]");O(Q,()=>{let R=G(Q),Y=new Set,X=R.items.length;R.items=R.items.filter((N)=>{let E=`${N.title}\x00${N.content}`;if(Y.has(E))return!1;return Y.add(E),!0});let Z=X-R.items.length;M(Q,R),D("info","Dedupe completed",{removed:Z,remaining:R.items.length}),q({ok:!0,removed:Z,remaining:R.items.length,message:`Dedupe removed ${Z} duplicate(s)`},z.json)});return}if(K==="stats"){O(Q,()=>{let R=G(Q),Y=R.items.length,X=R.items.filter((H)=>H.url).length,Z=R.items.filter((H)=>H.tags&&H.tags.length>0).length,N=Y>0?R.items.map((H)=>H.created_at).sort()[0]:null,E=Y>0?R.items.map((H)=>H.created_at).sort()[Y-1]:null,U={};for(let H of R.items)for(let V of H.tags||[])U[V]=(U[V]||0)+1;let _=Object.entries(U).sort((H,V)=>V[1]-H[1]).slice(0,5).map(([H,V])=>({tag:H,count:V}));q({ok:!0,total:Y,with_url:X,with_tags:Z,oldest:N,newest:E,top_tags:_,message:`${Y} items | ${X} with URL | ${Z} with tags`},z.json)});return}let J=z0(W[0]),C=J?` Did you mean '${J}'?`:"";throw D("warn","Unknown command",{input:W[0],suggestion:J}),Error(`Unknown command: ${W[0]}.${C} Run 'open-knowledge --help' for available commands.`)}if(import.meta.main)try{W0(process.argv.slice(2))}catch(B){let W=B instanceof Error?B.message:String(B);D("error","CLI error",{message:W,stack:B instanceof Error?B.stack:void 0}),console.error(`Error: ${W}`),process.exitCode=1}export{z0 as suggestCommand,R0 as sortItems,W0 as run,o as parseArgs};
57
+ --empty Remove items with empty content`)}function B0(R){if(R==="add"){console.log("Usage: open-knowledge add <title> <content> [--url <url>] [-t <tag>] [--json]");return}if(R==="list"||R==="ls"){console.log("Usage: open-knowledge list|ls [--format table|json] [-p <page>] [-l <limit>] [-s <search>] [-t <tag>] [--sort created|title] [--desc] [--json]");return}if(R==="get"){console.log("Usage: open-knowledge get --id <id> [--json]");return}if(R==="update"||R==="edit"){console.log("Usage: open-knowledge update|edit --id <id> [--title <title>] [--content <content>] [--url <url>] [-t <tag>] [--json]");return}if(R==="delete"||R==="rm"){console.log("Usage: open-knowledge delete|rm --id <id> -y [--json]");return}if(R==="export"){console.log("Usage: open-knowledge export [--format jsonl] [--json]");return}if(R==="prune"){console.log("Usage: open-knowledge prune --yes [--older-than <days>] [--empty] [--json]");return}if(R==="dedupe"){console.log("Usage: open-knowledge dedupe --yes [--json]");return}if(R==="stats"){console.log("Usage: open-knowledge stats [--json]");return}R0()}function K0(R){if(R.noColor||process.env.NO_COLOR)return!1;if(process.env.FORCE_COLOR)return!0;return process.stdout.isTTY===!0}function O(R,W,z){if(W){console.log(JSON.stringify(R,null,2));return}if(typeof R==="string"){console.log(R);return}console.log(R.message??JSON.stringify(R,null,2))}function x(R){if(!R.id)throw Error("Missing required --id. Example: open-knowledge get --id <id>")}function Q0(R,W){let z=W.sort??"created";if(z!=="created"&&z!=="title")throw Error("Invalid --sort value. Use 'created' or 'title'.");let B=[...R].sort((K,N)=>{if(z==="title")return K.title.localeCompare(N.title);return K.created_at.localeCompare(N.created_at)});if(W.desc)B.reverse();return{sorted:B,sort:z,direction:W.desc?"desc":"asc"}}function W0(R){let{positional:W,flags:z}=o(R);if(T("debug","CLI invoked",{command:W[0],flags:{json:z.json,store:z.store}}),z.version){console.log(z.json?JSON.stringify({name:F.name,version:F.version},null,2):F.version);return}if(z.completions){let Q=z.completions;if(Q==="bash")console.log('_open_knowledge() { local cur; cur="${COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update delete export help ls rm edit --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --no-color --scope" -- "$cur")); }; complete -F _open_knowledge open-knowledge');else if(Q==="zsh")console.log(`#compdef open-knowledge
58
+ _open_knowledge() { _arguments -C "1: :(add list get update delete export help ls rm edit)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"{created,title}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--no-color)--no-color[disable color]" "(--scope)--scope"{local,global,project}:" }; _open_knowledge`);else if(Q==="fish")console.log('complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update delete export help ls rm edit"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"');else throw Error("Invalid --completions value. Use 'bash', 'zsh', or 'fish'.");return}let B=t(W[0]);if(!B||z.help||B==="help"){B0(W[1]);return}let K=z.store;if(!K)if(z.scope==="project")K="./.open-knowledge/db.json";else K=p();if(b(K),B==="add"){let Q=W[1],Y=W[2];if(!Q||!Y)throw Error("Usage: open-knowledge add <title> <content>");G(K,()=>{let X=V(K),Z={id:v(),title:Q,content:Y,url:z.url??null,tags:z.tag?[z.tag]:[],created_at:new Date().toISOString(),updated_at:new Date().toISOString()};X.items.push(Z),j(K,X),T("info","Item added",{id:Z.id,title:Z.title}),O({ok:!0,item:Z,message:`Added ${Z.id}`},z.json)});return}if(B==="list"){if(z.format!==void 0&&z.format!=="table"&&z.format!=="json")throw Error("Invalid --format value for list. Use 'table' or 'json'.");G(K,()=>{let Q=V(K),Y=Number.isFinite(z.page)&&z.page>0?z.page:1,X=Number.isFinite(z.limit)&&z.limit>0?z.limit:20,Z=z.search?String(z.search).toLowerCase():"",E=z.tag?String(z.tag).toLowerCase():"",_=z.format==="table"||!z.json&&!z.format&&K0(z),A=z.json||z.format==="json",D=Q.items;if(Z)D=D.filter(($)=>$.title.toLowerCase().includes(Z)||$.content.toLowerCase().includes(Z));if(E)D=D.filter(($)=>$.tags&&$.tags.map((I)=>I.toLowerCase()).includes(E));let{sorted:H,sort:J,direction:C}=Q0(D,z),w=(Y-1)*X,M=H.slice(w,w+X),y=Math.max(1,Math.ceil(H.length/X));if(A){O({ok:!0,page:Y,limit:X,total:H.length,total_pages:y,sort:J,direction:C,items:M},!0);return}if(M.length===0){O(`No items found (search=${Z||"none"}, tag=${E||"none"})`,!1);return}if(_){let $=(q)=>q,I=`${$("ID")} ${$("TITLE")} ${$("CREATED")} ${$("URL")} ${$("TAGS")}`;console.log(I);for(let q of M)console.log(`${q.id} ${$(q.title)} ${q.created_at} ${q.url?$(q.url):""} ${q.tags?.length?$(`[${q.tags.join(", ")}]`):""}`);console.log(`Page ${Y}/${y} | showing ${M.length} of ${H.length} | sort=${J} ${C} | search=${Z||"none"} | tag=${E||"none"}`)}else{for(let $ of M)console.log(`${$.id} ${$.title} ${$.created_at}${$.url?` ${$.url}`:""}${$.tags?.length?` [${$.tags.join(", ")}]`:""}`);console.log(`Page ${Y}/${y} | showing ${M.length} of ${H.length} | sort=${J} ${C} | search=${Z||"none"} | tag=${E||"none"}`)}});return}if(B==="get"){x(z),G(K,()=>{let Y=V(K).items.find((X)=>X.id===z.id);if(!Y)throw Error(`Item not found: ${z.id}`);O({ok:!0,item:Y,message:`${Y.id}: ${Y.title}`},z.json)});return}if(B==="update"){x(z),G(K,()=>{let Q=V(K),Y=Q.items.findIndex((Z)=>Z.id===z.id);if(Y===-1)throw Error(`Item not found: ${z.id}`);let X=Q.items[Y];if(z.title!==void 0)X.title=z.title;if(z.content!==void 0)X.content=z.content;if(z.url!==void 0)X.url=z.url;if(z.tag!==void 0){if(X.tags=X.tags||[],!X.tags.map((Z)=>Z.toLowerCase()).includes(z.tag.toLowerCase()))X.tags.push(z.tag)}X.updated_at=new Date().toISOString(),Q.items[Y]=X,j(K,Q),O({ok:!0,item:X,message:`Updated ${X.id}`},z.json)});return}if(B==="delete"){if(x(z),!z.yes)throw Error("Refusing delete without --yes. Re-run with: open-knowledge delete --id <id> --yes");G(K,()=>{let Q=V(K),Y=Q.items.length;Q.items=Q.items.filter((Z)=>Z.id!==z.id);let X=Y!==Q.items.length;if(j(K,Q),!X)throw Error(`Item not found: ${z.id}`);T("info","Item deleted",{id:z.id}),O({ok:!0,deleted_id:z.id,message:`Deleted ${z.id}`},z.json)});return}if(B==="export"){let Q=z.format??"json";if(Q!=="json"&&Q!=="jsonl")throw Error("Invalid --format. Use 'json' or 'jsonl'.");G(K,()=>{let Y=V(K);if(Q==="jsonl")for(let X of Y.items)console.log(JSON.stringify(X));else O({ok:!0,items:Y.items},z.json)});return}if(B==="prune"){if(!z.yes)throw Error("Refusing prune without --yes. Re-run with: open-knowledge prune --yes [--older-than <days>] [--empty]");G(K,()=>{let Q=V(K),Y=Q.items.length;if(z.olderThan!==void 0){let Z=new Date;Z.setDate(Z.getDate()-z.olderThan),Q.items=Q.items.filter((E)=>new Date(E.created_at)>=Z)}if(z.empty)Q.items=Q.items.filter((Z)=>Z.content.trim().length>0);let X=Y-Q.items.length;j(K,Q),T("info","Prune completed",{pruned:X,remaining:Q.items.length}),O({ok:!0,pruned:X,remaining:Q.items.length,message:`Pruned ${X} item(s)`},z.json)});return}if(B==="dedupe"){if(!z.yes)throw Error("Refusing dedupe without --yes. Re-run with: open-knowledge dedupe --yes [--json]");G(K,()=>{let Q=V(K),Y=new Set,X=Q.items.length;Q.items=Q.items.filter((E)=>{let _=`${E.title}\x00${E.content}`;if(Y.has(_))return!1;return Y.add(_),!0});let Z=X-Q.items.length;j(K,Q),T("info","Dedupe completed",{removed:Z,remaining:Q.items.length}),O({ok:!0,removed:Z,remaining:Q.items.length,message:`Dedupe removed ${Z} duplicate(s)`},z.json)});return}if(B==="stats"){G(K,()=>{let Q=V(K),Y=Q.items.length,X=Q.items.filter((H)=>H.url).length,Z=Q.items.filter((H)=>H.tags&&H.tags.length>0).length,E=Y>0?Q.items.map((H)=>H.created_at).sort()[0]:null,_=Y>0?Q.items.map((H)=>H.created_at).sort()[Y-1]:null,A={};for(let H of Q.items)for(let J of H.tags||[])A[J]=(A[J]||0)+1;let D=Object.entries(A).sort((H,J)=>J[1]-H[1]).slice(0,5).map(([H,J])=>({tag:H,count:J}));O({ok:!0,total:Y,with_url:X,with_tags:Z,oldest:E,newest:_,top_tags:D,message:`${Y} items | ${X} with URL | ${Z} with tags`},z.json)});return}let N=z0(W[0]),U=N?` Did you mean '${N}'?`:"";throw T("warn","Unknown command",{input:W[0],suggestion:N}),Error(`Unknown command: ${W[0]}.${U} Run 'open-knowledge --help' for available commands.`)}if(import.meta.main)try{W0(process.argv.slice(2))}catch(R){let W=R instanceof Error?R.message:String(R);T("error","CLI error",{message:W,stack:R instanceof Error?R.stack:void 0}),console.error(`Error: ${W}`),process.exitCode=1}export{z0 as suggestCommand,Q0 as sortItems,W0 as run,o as parseArgs};
package/package.json CHANGED
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "name": "@hasna/knowledge",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
5
5
  "type": "module",
6
6
  "bin": {
7
- "open-knowledge": "./src/cli.ts",
8
- "open-knowledge-mcp": "./src/mcp.js"
7
+ "open-knowledge": "./bin/open-knowledge.js",
8
+ "open-knowledge-mcp": "./bin/open-knowledge-mcp.js"
9
9
  },
10
+ "files": [
11
+ "bin",
12
+ "src/mcp-http.js",
13
+ "src/store.ts",
14
+ "LICENSE",
15
+ "README.md"
16
+ ],
10
17
  "scripts": {
11
18
  "test": "bun test",
12
19
  "test:cli": "bun test tests/cli.test.ts",
13
- "build": "bun build --target=bun --outfile=bin/open-knowledge.js --minify src/cli.ts",
20
+ "build": "bun build --target=bun --outfile=bin/open-knowledge.js --minify src/cli.ts && bun build --target=bun --outfile=bin/open-knowledge-mcp.js --external @modelcontextprotocol/sdk src/mcp.js",
14
21
  "prepublishOnly": "bun run build",
15
22
  "postinstall": "bun run build"
16
23
  },
@@ -24,6 +31,10 @@
24
31
  "store"
25
32
  ],
26
33
  "license": "Apache-2.0",
34
+ "publishConfig": {
35
+ "registry": "https://registry.npmjs.org",
36
+ "access": "public"
37
+ },
27
38
  "repository": {
28
39
  "type": "git",
29
40
  "url": "https://github.com/hasna/knowledge"
@@ -1,59 +0,0 @@
1
- name: Bug Report
2
- description: Report a bug in open-knowledge
3
- labels: [bug]
4
- body:
5
- - type: markdown
6
- attributes:
7
- value: |
8
- Thanks for reporting a bug!
9
- - type: textarea
10
- id: description
11
- attributes:
12
- label: Bug Description
13
- description: A clear description of the bug
14
- validations:
15
- required: true
16
- - type: textarea
17
- id: steps
18
- attributes:
19
- label: Steps to Reproduce
20
- description: |
21
- 1.
22
- 2.
23
- 3.
24
- validations:
25
- required: true
26
- - type: textarea
27
- id: expected
28
- attributes:
29
- label: Expected Behavior
30
- validations:
31
- required: true
32
- - type: textarea
33
- id: actual
34
- attributes:
35
- label: Actual Behavior
36
- validations:
37
- required: true
38
- - type: input
39
- id: version
40
- attributes:
41
- label: Version
42
- description: Output of `open-knowledge --version`
43
- - type: dropdown
44
- id: os
45
- attributes:
46
- label: Operating System
47
- options:
48
- - macOS
49
- - Linux
50
- - Windows
51
- - Other
52
- - type: dropdown
53
- id: runtime
54
- attributes:
55
- label: Runtime
56
- options:
57
- - Bun
58
- - Node.js
59
- - Other
@@ -1,34 +0,0 @@
1
- name: Feature Request
2
- description: Suggest a new feature or improvement
3
- labels: [enhancement]
4
- body:
5
- - type: markdown
6
- attributes:
7
- value: |
8
- Ideas are welcome! The best features solve real problems for AI agents and CLI users.
9
- - type: textarea
10
- id: problem
11
- attributes:
12
- label: Problem or Motivation
13
- description: What problem does this solve?
14
- validations:
15
- required: true
16
- - type: textarea
17
- id: solution
18
- attributes:
19
- label: Proposed Solution
20
- description: How would you like it to work?
21
- validations:
22
- required: true
23
- - type: textarea
24
- id: alternatives
25
- attributes:
26
- label: Alternatives Considered
27
- description: Any other approaches you considered?
28
- - type: checkboxes
29
- id: willingness
30
- attributes:
31
- label: Willingness to Implement
32
- options:
33
- - label: I am willing to implement this feature
34
- - label: I can help test a PR for this feature
@@ -1,21 +0,0 @@
1
- ## Summary
2
-
3
- <!-- 1-3 sentence description of the change -->
4
-
5
- ## Motivation
6
-
7
- <!-- Why is this change needed? What problem does it solve? -->
8
-
9
- ## Changes
10
-
11
- <!-- Bulleted list of what was changed -->
12
-
13
- ## Testing
14
-
15
- <!-- How was this tested? -->
16
-
17
- ## Checklist
18
-
19
- - [ ] Tests added / updated
20
- - [ ] Documentation updated (if needed)
21
- - [ ] `bun test` passes
@@ -1,49 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- test:
11
- strategy:
12
- matrix:
13
- os: [ubuntu-latest, macos-latest]
14
- runtime: [bun, node]
15
- runs-on: ${{ matrix.os }}
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - name: Setup Bun
20
- if: matrix.runtime == 'bun'
21
- uses: oven-sh/setup-bun@v2
22
- with:
23
- bun-version: latest
24
-
25
- - name: Setup Node
26
- if: matrix.runtime == 'node'
27
- uses: actions/setup-node@v4
28
- with:
29
- node-version: latest
30
-
31
- - name: Install dependencies
32
- run: bun install
33
-
34
- - name: Run tests
35
- run: bun test
36
-
37
- test-matrix:
38
- strategy:
39
- matrix:
40
- os: [ubuntu-latest, macos-latest, windows-latest]
41
- runtime: [bun]
42
- runs-on: ${{ matrix.os }}
43
- steps:
44
- - uses: actions/checkout@v4
45
- - uses: oven-sh/setup-bun@v2
46
- with:
47
- bun-version: latest
48
- - run: bun install
49
- - run: bun test
@@ -1,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(connectors --help)"
5
- ]
6
- }
7
- }
@@ -1,31 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
-
7
- ## Our Standards
8
-
9
- Examples of behavior that contributes to a positive environment:
10
-
11
- * Using welcoming and inclusive language
12
- * Being respectful of differing viewpoints and experiences
13
- * Gracefully accepting constructive criticism
14
- * Focusing on what is best for the community
15
- * Showing empathy towards other community members
16
-
17
- Examples of unacceptable behavior:
18
-
19
- * The use of sexualized language or imagery and unwelcome sexual attention
20
- * Trolling, insulting/derogatory comments, and personal or political attacks
21
- * Public or private harassment
22
- * Publishing others' private information without explicit permission
23
- * Other conduct which could reasonably be considered inappropriate
24
-
25
- ## Enforcement
26
-
27
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate.
28
-
29
- ## Attribution
30
-
31
- This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
package/CONTRIBUTING.md DELETED
@@ -1,83 +0,0 @@
1
- # Contributing to open-knowledge
2
-
3
- Thank you for your interest in contributing!
4
-
5
- ## Development Setup
6
-
7
- ```bash
8
- # Clone the repo
9
- git clone https://github.com/hasna/knowledge.git
10
- cd knowledge
11
-
12
- # Install dependencies (Bun)
13
- bun install
14
-
15
- # Run tests
16
- bun test
17
-
18
- # Run a specific test file
19
- bun test tests/cli.test.ts
20
- ```
21
-
22
- ## Project Structure
23
-
24
- ```
25
- knowledge/
26
- ├── src/
27
- │ ├── cli.js # CLI entry point, argument parsing, commands
28
- │ └── store.js # Persistent store, file locking, ID generation
29
- ├── tests/
30
- │ └── cli.test.ts # Integration tests using Bun.test
31
- ├── package.json
32
- └── LICENSE
33
- ```
34
-
35
- ## Design Principles
36
-
37
- **Agent-friendly first**: every output should be parseable by an LLM. Prefer `--json` for structured data. Keep error messages actionable.
38
-
39
- **Minimal dependencies**: keep the dependency footprint small. The store is a plain JSON file.
40
-
41
- **Safe by default**: destructive operations require explicit confirmation flags (`--yes`).
42
-
43
- **Concurrent-safe**: all store mutations go through `withLock()`. Do not bypass it.
44
-
45
- ## Commit Conventions
46
-
47
- Use [Conventional Commits](https://www.conventionalcommits.org/):
48
-
49
- ```
50
- feat(cli): add --tag filter on list command
51
- fix(store): handle empty store file gracefully
52
- docs(readme): add installation instructions
53
- ```
54
-
55
- Types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`
56
-
57
- ## Pull Request Process
58
-
59
- 1. Fork the repo and create a branch from `main`.
60
- 2. Add tests for new functionality.
61
- 3. Ensure all tests pass: `bun test`.
62
- 4. Keep commits atomic and well-described.
63
- 5. Open a PR with a clear description of the change and motivation.
64
-
65
- ## Code Style
66
-
67
- - 2-space indentation
68
- - `for` loops over array methods where performance matters
69
- - Descriptive variable names
70
- - No unnecessary dependencies
71
-
72
- ## Reporting Issues
73
-
74
- - Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.yml)
75
- - Search existing issues first
76
- - Include: Node/Bun version, OS, steps to reproduce, expected vs actual
77
-
78
- ## Suggesting Features
79
-
80
- Open a [feature request issue](.github/ISSUE_TEMPLATE/feature_request.yml) describing:
81
- - The problem you're solving
82
- - How you envision the solution
83
- - Whether you're willing to implement it
package/FUNDING.yml DELETED
@@ -1 +0,0 @@
1
- github: hasna
package/SECURITY.md DELETED
@@ -1,39 +0,0 @@
1
- # Security Policy
2
-
3
- ## Supported Versions
4
-
5
- | Version | Supported |
6
- | ------- | ------------------ |
7
- | 0.1.x | :white_check_mark: |
8
-
9
- ## Reporting a Vulnerability
10
-
11
- If you discover a security vulnerability, please report it responsibly.
12
-
13
- **Do not open a public GitHub issue** for security vulnerabilities.
14
-
15
- Please send details privately:
16
-
17
- 1. **Email**: Send to the maintainer directly via GitHub.
18
- 2. **GitHub Security Advisories**: Use the [Security Advisories](https://github.com/hasna/knowledge/security/advisories/new) feature to report privately.
19
-
20
- Include in your report:
21
- - Description of the vulnerability
22
- - Steps to reproduce
23
- - Potential impact
24
- - Any suggested fixes (optional)
25
-
26
- ## Response Timeline
27
-
28
- - **Acknowledgment**: within 48 hours
29
- - **Initial assessment**: within 5 days
30
- - **Fix timeline**: depends on severity; critical issues are addressed immediately
31
-
32
- ## Scope
33
-
34
- This project stores data in a local JSON file (`~/.open-knowledge/db.json` by default). Security considerations:
35
-
36
- - Store file permissions should be restricted to the owner
37
- - No network access or remote code execution
38
- - No authentication (local CLI tool)
39
- - Encryption of the store file is not currently implemented
package/npmignore DELETED
@@ -1,9 +0,0 @@
1
- tests/
2
- .github/
3
- .gitignore
4
- .git/
5
- .env*
6
- *.test.ts
7
- *.test.js
8
- tsconfig.json
9
- bun.lockb