@hasna/knowledge 0.2.0 → 0.2.1

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 I,writeFileSync as L,existsSync as u,renameSync as f,unlinkSync as b}from"fs";import{dirname as m}from"path";import{homedir as i}from"os";import{randomUUID as P}from"crypto";function v(){return`${i()}/.open-knowledge/db.json`}function k(B){if(!u(B))c(m(B),{recursive:!0}),L(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(!u(B)){L(B,JSON.stringify({owner:W,ts:Date.now()}));return}let A=JSON.parse(I(B,"utf8"));if(Date.now()-A.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(u(B)){if(JSON.parse(I(B,"utf8")).owner===W)b(B)}}catch{}}function O(B){k(B);let W=I(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()}`;L(z,JSON.stringify(W,null,2)),f(z,B)}function q(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 x={name:"@hasna/knowledge",version:"0.2.0",description:"Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",type:"module",bin:{"open-knowledge":"./src/cli.ts"},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"}};var h={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 D(B,W,z){if(h[B]<h[s()])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 e=["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=[...e,...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 f,unlinkSync as b}from"fs";import{dirname as m}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(m(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)),f(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.1",description:"Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",type:"module",bin:{"open-knowledge":"./src/cli.ts"},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"}};var h={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 D(B,W,z){if(h[B]<h[s()])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 e=["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=[...e,...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
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 V(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){V({name:x.name,version:x.version},z.json);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(k(Q),K==="add"){let R=W[1],Y=W[2];if(!R||!Y)throw Error("Usage: open-knowledge add <title> <content>");q(Q,()=>{let X=O(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}),V({ok:!0,item:Z,message:`Added ${Z.id}`},z.json)});return}if(K==="list"){q(Q,()=>{let R=O(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((y)=>y.toLowerCase()).includes(N));let{sorted:H,sort:G,direction:C}=R0(_,z),w=(Y-1)*X,F=H.slice(w,w+X),j=Math.max(1,Math.ceil(H.length/X));if(U){V({ok:!0,page:Y,limit:X,total:H.length,total_pages:j,sort:G,direction:C,items:F},!0);return}if(F.length===0){V(`No items found (search=${Z||"none"}, tag=${N||"none"})`,!1);return}if(E){let $=(T)=>T,y=`${$("ID")} ${$("TITLE")} ${$("CREATED")} ${$("URL")} ${$("TAGS")}`;console.log(y);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}/${j} | showing ${F.length} of ${H.length} | sort=${G} ${C} | 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}/${j} | showing ${F.length} of ${H.length} | sort=${G} ${C} | search=${Z||"none"} | tag=${N||"none"}`)}});return}if(K==="get"){S(z),q(Q,()=>{let Y=O(Q).items.find((X)=>X.id===z.id);if(!Y)throw Error(`Item not found: ${z.id}`);V({ok:!0,item:Y,message:`${Y.id}: ${Y.title}`},z.json)});return}if(K==="update"){S(z),q(Q,()=>{let R=O(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),V({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");q(Q,()=>{let R=O(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}),V({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'.");q(Q,()=>{let Y=O(Q);if(R==="jsonl")for(let X of Y.items)console.log(JSON.stringify(X));else V({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]");q(Q,()=>{let R=O(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}),V({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]");q(Q,()=>{let R=O(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}),V({ok:!0,removed:Z,remaining:R.items.length,message:`Dedupe removed ${Z} duplicate(s)`},z.json)});return}if(K==="stats"){q(Q,()=>{let R=O(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 G of H.tags||[])U[G]=(U[G]||0)+1;let _=Object.entries(U).sort((H,G)=>G[1]-H[1]).slice(0,5).map(([H,G])=>({tag:H,count:G}));V({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]),A=J?` Did you mean '${J}'?`:"";throw D("warn","Unknown command",{input:W[0],suggestion:J}),Error(`Unknown command: ${W[0]}.${A} 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 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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/knowledge",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -207,7 +207,7 @@ function useColor(flags: Flags): boolean {
207
207
  return process.stdout.isTTY === true;
208
208
  }
209
209
 
210
- function output(data: unknown, asJson: boolean | undefined, flags?: Flags): void {
210
+ function output(data: unknown, asJson?: boolean, _flags?: Flags): void {
211
211
  if (asJson) { console.log(JSON.stringify(data, null, 2)); return; }
212
212
  if (typeof data === 'string') { console.log(data); return; }
213
213
  console.log((data as { message?: string }).message ?? JSON.stringify(data, null, 2));
@@ -234,7 +234,7 @@ function run(argv: string[]): void {
234
234
  const { positional, flags } = parseArgs(argv);
235
235
  log('debug', 'CLI invoked', { command: positional[0], flags: { json: flags.json, store: flags.store } });
236
236
 
237
- if (flags.version) { output({ name: pkg.name, version: pkg.version }, flags.json); return; }
237
+ if (flags.version) { console.log(flags.json ? JSON.stringify({ name: pkg.name, version: pkg.version }, null, 2) : pkg.version); return; }
238
238
 
239
239
  if (flags.completions) {
240
240
  const shell = flags.completions;
@@ -290,6 +290,9 @@ function run(argv: string[]): void {
290
290
  }
291
291
 
292
292
  if (command === 'list') {
293
+ if (flags.format !== undefined && flags.format !== 'table' && flags.format !== 'json') {
294
+ throw new Error("Invalid --format value for list. Use 'table' or 'json'.");
295
+ }
293
296
  withLock(storePath, () => {
294
297
  const db = loadStore(storePath);
295
298
  const page = Number.isFinite(flags.page) && (flags.page as number) > 0 ? flags.page as number : 1;