nx-kv 0.1.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -0
  4. package/assets/nx-kv/SKILL.md +99 -0
  5. package/assets/nx-kv/references/agent-workflow.md +178 -0
  6. package/assets/nx-kv/references/todo-commands.md +193 -0
  7. package/bin/cli.mjs +10 -0
  8. package/package.json +57 -0
  9. package/src/core/config.js +94 -0
  10. package/src/core/errors.js +81 -0
  11. package/src/core/frontmatter.js +65 -0
  12. package/src/core/fstree.js +74 -0
  13. package/src/core/kvapi.js +201 -0
  14. package/src/core/open.js +26 -0
  15. package/src/core/paths.js +46 -0
  16. package/src/core/prompt.js +60 -0
  17. package/src/index.js +9 -0
  18. package/src/modules/auth/index.js +98 -0
  19. package/src/modules/auth/view.jsx +78 -0
  20. package/src/modules/bundled/index.js +51 -0
  21. package/src/modules/bundled/service.js +80 -0
  22. package/src/modules/group/index.js +199 -0
  23. package/src/modules/group/view.jsx +159 -0
  24. package/src/modules/system/index.js +153 -0
  25. package/src/modules/todo/index.js +280 -0
  26. package/src/modules/todo/service.js +524 -0
  27. package/src/modules/todo/view.jsx +280 -0
  28. package/src/runtime/api.js +108 -0
  29. package/src/runtime/cli.js +299 -0
  30. package/src/runtime/registry.js +56 -0
  31. package/src/runtime/server.js +74 -0
  32. package/src/runtime/spec.js +147 -0
  33. package/src/web/frontend/App.jsx +74 -0
  34. package/src/web/frontend/api/client.js +39 -0
  35. package/src/web/frontend/components/CliHints.jsx +25 -0
  36. package/src/web/frontend/components/ui.jsx +197 -0
  37. package/src/web/frontend/index.html +13 -0
  38. package/src/web/frontend/main.jsx +17 -0
  39. package/src/web/frontend/registry.js +12 -0
  40. package/src/web/frontend/store.jsx +74 -0
  41. package/src/web/frontend/style.css +368 -0
  42. package/src/web/public/assets/CliHints-3H3KOoDZ.js +1 -0
  43. package/src/web/public/assets/index-BnWex75m.css +1 -0
  44. package/src/web/public/assets/index-jhTtYCb9.js +10 -0
  45. package/src/web/public/assets/view-CrL-DSJu.js +1 -0
  46. package/src/web/public/assets/view-Du7Wv_-h.js +1 -0
  47. package/src/web/public/assets/view-S0N5g6v1.js +2 -0
  48. package/src/web/public/index.html +13 -0
@@ -0,0 +1,2 @@
1
+ import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-jhTtYCb9.js";import{t as l}from"./CliHints-3H3KOoDZ.js";var u=r(),d=s(),f=[[`open`,`待办`],[`done`,`已完成`],[`freeze`,`冻结`]],p=e=>e?String(e).slice(0,16).replace(`T`,` `):``,m=15,h=(e,t)=>`${e}:${t.id}`;function g({bucket:e,task:t,dupCount:n,onAct:r}){let i=e===`done`,a=e===`freeze`,o=i?t.doneAt:a?t.frozenAt:t.createdAt;return(0,d.jsxs)(`div`,{className:`row`,children:[(0,d.jsxs)(`span`,{className:`tid`,children:[`#`,t.id]}),(0,d.jsx)(c,{className:`topic`,text:t.topic,title:`点击复制主题`,children:t.topic}),(0,d.jsx)(`span`,{className:`text`,title:t.text,children:t.text}),n>1?(0,d.jsxs)(`span`,{className:`tag`,title:`同 id 有 ${n} 条,操作时需要消歧`,children:[`id×`,n]}):null,(0,d.jsx)(`span`,{className:`muted time`,children:p(o)}),(0,d.jsxs)(`span`,{className:`acts`,children:[e===`open`?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>r(`done`,t),children:`完成`}),(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>r(`freeze`,t),children:`冻结`})]}):null,a?(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>r(`unfreeze`,t),children:`解冻`}):null,(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>r(`edit`,t,e),children:`编辑`}),(0,d.jsx)(`button`,{className:`btn small ghost danger`,onClick:()=>r(`remove`,t,e),children:`删除`})]}),t.note?(0,d.jsx)(`div`,{className:`note`,children:t.note}):null]})}function _(){let{boot:r,ui:s,patchUi:c,refreshBoot:_}=a(),v=e(),y=n(),{dialog:b,node:x}=o(),[S,C]=(0,u.useState)(null),[w,T]=(0,u.useState)(!1),[E,D]=(0,u.useState)({topic:``,text:``}),[O,k]=(0,u.useState)(null),[A,j]=(0,u.useState)({}),M=r?.groupId??0,N=(r?.groups||[]).find(e=>e.id===M)?.name||(M?`#${M}`:`默认组`),P=(0,u.useCallback)(async()=>{if(r?.loggedIn)try{C(await t(`/api/todo`))}catch(e){v(e.message)}},[r?.loggedIn,v]);(0,u.useEffect)(()=>{P()},[P,M]);let F=(0,u.useCallback)(async()=>{T(!0);try{await P()}finally{T(!1)}},[P]),I=(0,u.useMemo)(()=>{let e=new Map;if(!S)return e;for(let[t]of f)for(let n of S[t]||[])e.set(n.id,(e.get(n.id)||0)+1);return e},[S]),L=(0,u.useMemo)(()=>{if(!S)return null;let e=s.topic,t=t=>t?e?t.filter(t=>t.topic===e):t:null;return{open:t(S.open),done:t(S.done),freeze:t(S.freeze)}},[S,s.topic]);if(!r?.loggedIn)return(0,d.jsxs)(`div`,{className:`card`,style:{padding:16},children:[(0,d.jsx)(`div`,{className:`colhead`,children:(0,d.jsx)(`h3`,{children:`请先登录`})}),(0,d.jsxs)(`div`,{className:`muted`,children:[`在「登录」页填写邮箱与密码,或执行 `,(0,d.jsx)(`code`,{children:`nx-kv auth login <email>`}),`。`]})]});let R=async(e,t)=>{let n=[];for(let[t]of f)for(let r of S[t]||[])r.id===e.id&&n.push({bucket:t,task:r});if(n.length<=1)return{bucket:t,task:e,pick:void 0};let r=n.findIndex(n=>n.bucket===t&&n.task.text===e.text),i=await b({title:`id #${e.id} 有 ${n.length} 条,选哪一条?`,message:n.map((e,t)=>`[${t}] ${e.bucket} ${p(e.task.createdAt)} ${e.task.text.slice(0,40)}`).join(`
2
+ `),input:!0,placeholder:`输入 0..${n.length-1}`,value:String(r>=0?r:0)});if(i===null)return null;let a=Number(i);return!Number.isInteger(a)||!n[a]?(v(`请输入 0..${n.length-1}`),null):{...n[a],pick:a}},z=(e,n,r)=>y(async()=>{let i=e===`remove`||e===`edit`?await R(n,r):{bucket:r,task:n,pick:void 0};if(!i)return;let a={group:M,pick:i.pick};if(e===`done`){let e=await b({title:`完成结果`,message:`完成「${n.text.slice(0,40)}」`,input:!0,placeholder:`可选:写一句完成结果`});if(e===null)return;await t(`/api/todo/${n.id}/done`,{method:`POST`,body:{...a,result:e||void 0}}),v(`已完成`)}else if(e===`freeze`)await t(`/api/todo/${n.id}/freeze`,{method:`POST`,body:a}),v(`已冻结`);else if(e===`unfreeze`)await t(`/api/todo/${n.id}/unfreeze`,{method:`POST`,body:a}),v(`已解冻`);else if(e===`remove`){if(!await b({message:`删除 #${n.id}「${n.text.slice(0,40)}」?`,danger:!0}))return;await t(`/api/todo/${i.task.id}`,{method:`DELETE`,body:a}),v(`已删除`)}else if(e===`edit`){let e=await b({title:`编辑内容`,input:!0,value:i.task.text});if(e===null||!e)return;await t(`/api/todo/${i.task.id}`,{method:`PATCH`,body:{...a,text:e,matchTopic:i.task.topic}}),v(`已更新`)}await F(),await _()}),B=()=>y(async()=>{let e=await b({title:`归档已完成的旧记录`,message:`把「完成时间早于该日期」的记录移到冷 key(默认 30 天前)。留空用默认值。`,input:!0,placeholder:`YYYY-MM-DD,留空 = 30 天前`});if(e===null)return;let n=await t(`/api/todo/archive`,{method:`POST`,body:{before:e.trim()||void 0,group:M}});v(n.moved?`已归档 ${n.moved} 条 → ${n.coldKey}`:`没有需要归档的记录`),await F()}),V=()=>y(async()=>{let e=E.topic.trim(),n=E.text.trim();if(!e){v(`请填主题`);return}if(!n){v(`请填内容`);return}let r=await t(`/api/todo`,{method:`POST`,body:{topic:e,text:n,group:M}});D({topic:e,text:``}),v(`已加入待办 #${r.id}${r.topicAdded?`(新主题 ${e})`:``}`),await F()}),H=(e,t)=>A[e]?t:t.slice(0,m),U=S?.topics||[],W=L?f.map(([e,t])=>`${t} ${(L[e]||[]).length}`).join(` · `):``;return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`toolbar`,children:[(0,d.jsx)(`label`,{children:`工作空间`}),(0,d.jsx)(`span`,{className:`pill on`,children:N}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`label`,{children:`主题`}),(0,d.jsxs)(`select`,{value:s.topic,onChange:e=>c({topic:e.target.value}),children:[(0,d.jsx)(`option`,{value:``,children:`全部`}),U.map(e=>(0,d.jsx)(`option`,{value:e,children:e},e))]}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`input`,{placeholder:`主题`,size:`8`,value:E.topic,onChange:e=>D({...E,topic:e.target.value})}),(0,d.jsx)(`input`,{placeholder:`要做什么…`,size:`30`,value:E.text,onChange:e=>D({...E,text:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&V()}}),(0,d.jsx)(`button`,{className:`btn`,onClick:V,children:`加入待办`}),(0,d.jsx)(`button`,{className:`btn ghost`,onClick:F,disabled:w,children:w?`刷新中…`:`刷新`})]}),(0,d.jsx)(`div`,{className:`muted`,style:{margin:`0 0 8px 2px`,fontSize:12},children:W}),(0,d.jsx)(`div`,{className:`cols`,children:f.map(([e,t])=>(0,d.jsxs)(`div`,{className:`col`,children:[(0,d.jsxs)(`div`,{className:`colhead`,children:[(0,d.jsx)(`h3`,{children:t}),(0,d.jsx)(`span`,{className:`muted`,children:(L?.[e]||[]).length}),e===`done`&&(L?.done||[]).length?(0,d.jsx)(`button`,{className:`btn small ghost`,style:{marginLeft:`auto`},onClick:B,title:`把较旧的完成记录移到冷 key`,children:`归档旧记录`}):null]}),(0,d.jsxs)(`div`,{className:`card list`,children:[(L?.[e]||[]).length?H(e,L[e]).map(t=>(0,d.jsx)(g,{bucket:e,task:t,dupCount:I.get(t.id)||1,onAct:z},h(e,t))):(0,d.jsx)(`div`,{className:`row muted`,children:`(空)`}),(L?.[e]||[]).length>m?(0,d.jsx)(`div`,{className:`row muted`,style:{cursor:`pointer`,justifyContent:`center`},onClick:()=>j(t=>({...t,[e]:!t[e]})),children:A[e]?`收起`:`还有 ${L[e].length-m} 条,点击展开`}):null]})]},e))}),(0,d.jsx)(l,{module:`todo`}),O?(0,d.jsx)(i,{title:O.title,onClose:()=>k(null),children:O.node}):null,x]})}export{_ as default};
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>nx-kv · nx-kv</title>
7
+ <script type="module" crossorigin src="/assets/index-jhTtYCb9.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BnWex75m.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>