@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.
- package/bin/open-knowledge.js +3 -3
- package/package.json +1 -1
- package/src/cli.ts +5 -2
package/bin/open-knowledge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{mkdirSync as c,readFileSync as
|
|
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
|
|
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(
|
|
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
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
|
|
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) {
|
|
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;
|