@tchayen/oru 0.0.1 → 0.0.5

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/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- var Yn=Object.defineProperty;var q=(t,e)=>()=>(t&&(e=t(t=0)),e);var Ee=(t,e)=>{for(var n in e)Yn(t,n,{get:e[n],enumerable:!0})};import ie from"fs";import lt from"path";import br from"os";import{parse as hr}from"smol-toml";function fe(){return process.env.ORU_CONFIG_DIR?lt.join(process.env.ORU_CONFIG_DIR,"config.toml"):lt.join(br.homedir(),".oru","config.toml")}function ct(t){let e=t??fe();if(!ie.existsSync(e))return{...it};let n=ie.readFileSync(e,"utf-8"),o;try{o=hr(n)}catch(s){return process.stderr.write(`Warning: Could not parse config file at ${e}: ${s instanceof Error?s.message:String(s)}. Using defaults.
3
- `),{...it}}let r={...it};return typeof o.date_format=="string"&&kr.has(o.date_format)&&(r.date_format=o.date_format),typeof o.first_day_of_week=="string"&&Sr.has(o.first_day_of_week.toLowerCase())&&(r.first_day_of_week=o.first_day_of_week.toLowerCase()),typeof o.output_format=="string"&&vr.has(o.output_format)&&(r.output_format=o.output_format),typeof o.next_month=="string"&&Tr.has(o.next_month)&&(r.next_month=o.next_month),typeof o.auto_update_check=="boolean"&&(r.auto_update_check=o.auto_update_check),typeof o.telemetry=="boolean"&&(r.telemetry=o.telemetry),typeof o.telemetry_notice_shown=="boolean"&&(r.telemetry_notice_shown=o.telemetry_notice_shown),typeof o.backup_path=="string"&&o.backup_path.length>0&&(r.backup_path=o.backup_path),typeof o.backup_interval=="number"&&o.backup_interval>0&&(r.backup_interval=o.backup_interval),r}function ge(t,e){let n=fe(),o="";ie.existsSync(n)&&(o=ie.readFileSync(n,"utf-8"));let r=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),s=new RegExp(`^${r}\\s*=\\s*.*`,"m");s.test(o)?o=o.replace(s,`${t} = ${e}`):o=`${o.trimEnd()}
2
+ var Kn=Object.defineProperty;var K=(t,e)=>()=>(t&&(e=t(t=0)),e);var Ae=(t,e)=>{for(var n in e)Kn(t,n,{get:e[n],enumerable:!0})};import le from"fs";import bt from"path";import xr from"os";import{parse as Or}from"smol-toml";function ye(){return process.env.ORU_CONFIG_DIR?bt.join(process.env.ORU_CONFIG_DIR,"config.toml"):bt.join(xr.homedir(),".oru","config.toml")}function ht(t){let e=t??ye();if(!le.existsSync(e))return{...yt};let n=le.readFileSync(e,"utf-8"),o;try{o=Or(n)}catch(s){return process.stderr.write(`Warning: Could not parse config file at ${e}: ${s instanceof Error?s.message:String(s)}. Using defaults.
3
+ `),{...yt}}let r={...yt};return typeof o.date_format=="string"&&$r.has(o.date_format)&&(r.date_format=o.date_format),typeof o.first_day_of_week=="string"&&Dr.has(o.first_day_of_week.toLowerCase())&&(r.first_day_of_week=o.first_day_of_week.toLowerCase()),typeof o.output_format=="string"&&Nr.has(o.output_format)&&(r.output_format=o.output_format),typeof o.next_month=="string"&&Rr.has(o.next_month)&&(r.next_month=o.next_month),typeof o.auto_update_check=="boolean"&&(r.auto_update_check=o.auto_update_check),typeof o.telemetry=="boolean"&&(r.telemetry=o.telemetry),typeof o.telemetry_notice_shown=="boolean"&&(r.telemetry_notice_shown=o.telemetry_notice_shown),typeof o.backup_path=="string"&&o.backup_path.length>0&&(r.backup_path=o.backup_path),typeof o.backup_interval=="number"&&o.backup_interval>0&&(r.backup_interval=o.backup_interval),r}function be(t,e){let n=ye(),o="";le.existsSync(n)&&(o=le.readFileSync(n,"utf-8"));let r=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),s=new RegExp(`^${r}\\s*=\\s*.*`,"m");s.test(o)?o=o.replace(s,`${t} = ${e}`):o=`${o.trimEnd()}
4
4
  ${t} = ${e}
5
- `,ie.mkdirSync(lt.dirname(n),{recursive:!0}),ie.writeFileSync(n,o)}var it,kr,Sr,vr,Tr,sn,ut=q(()=>{"use strict";it={date_format:"mdy",first_day_of_week:"monday",output_format:"text",next_month:"same_day",auto_update_check:!0,telemetry:!0,telemetry_notice_shown:!1,backup_path:null,backup_interval:60},kr=new Set(["dmy","mdy"]),Sr=new Set(["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]),vr=new Set(["text","json"]),Tr=new Set(["same_day","first"]);sn=`# oru configuration
5
+ `,le.mkdirSync(bt.dirname(n),{recursive:!0}),le.writeFileSync(n,o)}var yt,$r,Dr,Nr,Rr,bn,kt=K(()=>{"use strict";yt={date_format:"mdy",first_day_of_week:"monday",output_format:"text",next_month:"same_day",auto_update_check:!0,telemetry:!0,telemetry_notice_shown:!1,backup_path:null,backup_interval:60},$r=new Set(["dmy","mdy"]),Dr=new Set(["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]),Nr=new Set(["text","json"]),Rr=new Set(["same_day","first"]);bn=`# oru configuration
6
6
  # Docs: https://github.com/tchayen/oru#configuration
7
7
 
8
8
  # Date input format for slash dates (e.g. 03/04/2026)
@@ -38,36 +38,36 @@ telemetry = true
38
38
 
39
39
  # Minimum minutes between auto-backups (default: 60)
40
40
  # backup_interval = 60
41
- `});var le=q(()=>{"use strict"});var W,Tn,ke=q(()=>{"use strict";W="0.0.1",Tn="80ce484"});var St={};Ee(St,{buildEvent:()=>Zr,detectCI:()=>wn,extractCommandAndFlags:()=>Kr,getTelemetryDisabledReason:()=>kt,isTelemetryEnabled:()=>ht,sendEvent:()=>zr,showFirstRunNotice:()=>Qr});function ht(t){return!(process.env.DO_NOT_TRACK==="1"||process.env.ORU_TELEMETRY_DISABLED==="1"||t.telemetry===!1)}function kt(t){return process.env.DO_NOT_TRACK==="1"?"disabled (via DO_NOT_TRACK)":process.env.ORU_TELEMETRY_DISABLED==="1"?"disabled (via ORU_TELEMETRY_DISABLED)":t.telemetry===!1?"disabled (via config)":null}function wn(){return qr.some(t=>process.env[t])}function Kr(t){let e=t.slice(2),n=[],o="",r=!1;for(let s=0;s<e.length;s++){let a=e[s];if(a.startsWith("-")){let u=a.includes("=")?a.slice(0,a.indexOf("=")):a;n.push(u),!a.includes("=")&&s+1<e.length&&!e[s+1].startsWith("-")&&s++}else r?r&&!o.includes(" ")&&Gr(o,a)&&(o=`${o} ${a}`):(o=a,r=!0)}return{command:o||"(unknown)",flags:n}}function Gr(t,e){return{config:["init","path"],filter:["add","list","show","remove"],completions:["bash","zsh","fish"],telemetry:["status","enable","disable"],...{}}[t]?.includes(e)??!1}function zr(t){let e=process.env.ORU_TELEMETRY_URL??Vr;try{let n=new AbortController,o=setTimeout(()=>n.abort(),Xr);fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:n.signal}).catch(r=>{process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",r)}).finally(()=>clearTimeout(o))}catch(n){process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",n)}}function Qr(t){try{if(t.telemetry_notice_shown)return;process.stderr.isTTY&&process.stderr.write(`
41
+ `});var de=K(()=>{"use strict"});var J,Mn,$e=K(()=>{"use strict";J="0.0.5",Mn="4753478"});var It={};Ae(It,{buildEvent:()=>io,detectCI:()=>Pn,extractCommandAndFlags:()=>ro,getTelemetryDisabledReason:()=>Rt,isTelemetryEnabled:()=>Nt,sendEvent:()=>so,showFirstRunNotice:()=>ao});function Nt(t){return!(process.env.DO_NOT_TRACK==="1"||process.env.ORU_TELEMETRY_DISABLED==="1"||t.telemetry===!1)}function Rt(t){return process.env.DO_NOT_TRACK==="1"?"disabled (via DO_NOT_TRACK)":process.env.ORU_TELEMETRY_DISABLED==="1"?"disabled (via ORU_TELEMETRY_DISABLED)":t.telemetry===!1?"disabled (via config)":null}function Pn(){return no.some(t=>process.env[t])}function ro(t){let e=t.slice(2),n=[],o="",r=!1;for(let s=0;s<e.length;s++){let a=e[s];if(a.startsWith("-")){let u=a.includes("=")?a.slice(0,a.indexOf("=")):a;n.push(u),!a.includes("=")&&s+1<e.length&&!e[s+1].startsWith("-")&&s++}else r?r&&!o.includes(" ")&&oo(o,a)&&(o=`${o} ${a}`):(o=a,r=!0)}return{command:o||"(unknown)",flags:n}}function oo(t,e){return{config:["init","path"],filter:["add","list","show","remove"],completions:["bash","zsh","fish"],telemetry:["status","enable","disable"],...{}}[t]?.includes(e)??!1}function so(t){let e=process.env.ORU_TELEMETRY_URL??eo;try{let n=new AbortController,o=setTimeout(()=>n.abort(),to);fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:n.signal}).catch(r=>{process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",r)}).finally(()=>clearTimeout(o))}catch(n){process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",n)}}function ao(t){try{if(t.telemetry_notice_shown)return;process.stderr.isTTY&&process.stderr.write(`
42
42
  oru collects anonymous usage data to improve the tool.
43
43
  To disable: oru telemetry disable (or set DO_NOT_TRACK=1)
44
44
 
45
- `),ge("telemetry_notice_shown","true")}catch(e){process.env.ORU_DEBUG==="1"&&console.error("Telemetry notice failed:",e)}}function Zr(t,e,n,o,r){let s={cli_version:W,command:t,flags:e,os:process.platform,arch:process.arch,node_version:process.version,is_ci:wn(),duration_ms:n,exit_code:o};return r!==void 0&&(s.error=r),s}var Vr,Xr,qr,We=q(()=>{"use strict";ut();le();ke();Vr="https://telemetry.oru.sh/v1/events",Xr=3e3;qr=["CI","GITHUB_ACTIONS","GITLAB_CI","CIRCLECI","TRAVIS","JENKINS_URL","BUILDKITE","TF_BUILD"]});var xn={};Ee(xn,{autoBackup:()=>to,performBackup:()=>Je,shouldAutoBackup:()=>On});import Se from"fs";import ve from"path";import En from"os";function eo(){return`oru-${new Date().toISOString().replace(/[:.]/g,"-").replace("Z","")}.db`}function Je(t,e){let n=e.startsWith("~")?ve.join(En.homedir(),e.slice(1)):e;Se.mkdirSync(n,{recursive:!0});let o=eo(),r=o.slice(0,-3),s=ve.join(n,o),a=1;for(;Se.existsSync(s);)s=ve.join(n,`${r}-${a}.db`),a++;return t.exec(`VACUUM INTO '${s.replace(/'/g,"''")}'`),s}function On(t,e){let n=t.startsWith("~")?ve.join(En.homedir(),t.slice(1)):t;if(!Se.existsSync(n))return!0;let o=Se.readdirSync(n).filter(a=>a.startsWith("oru-")&&a.endsWith(".db")).sort();if(o.length===0)return!0;let r=Se.statSync(ve.join(n,o[o.length-1]));return(Date.now()-r.mtimeMs)/1e3/60>=e}function to(t,e,n){try{On(e,n)&&Je(t,e)}catch(o){process.env.ORU_DEBUG==="1"&&console.error("Auto-backup failed:",o)}}var vt=q(()=>{"use strict"});async function He(){try{let t=new AbortController,e=setTimeout(()=>t.abort(),1e4),n=await fetch("https://registry.npmjs.org/@tchayen/oru/latest",{signal:t.signal});return clearTimeout(e),n.ok?(await n.json()).version??null:null}catch{return null}}var Tt=q(()=>{"use strict"});var Ot={};Ee(Ot,{checkForUpdate:()=>ao,compareVersions:()=>Te,printUpdateNotice:()=>io});import wt from"fs";import Et from"path";import no from"os";function $n(){let t=process.env.ORU_INSTALL_DIR??Et.join(no.homedir(),".oru");return Et.join(t,".update-state.json")}function oo(){try{let t=wt.readFileSync($n(),"utf-8");return JSON.parse(t)}catch{return null}}function so(t){let e=$n();wt.mkdirSync(Et.dirname(e),{recursive:!0}),wt.writeFileSync(e,JSON.stringify(t))}function Te(t,e){let n=t.split(".").map(Number),o=e.split(".").map(Number);for(let r=0;r<3;r++){let s=n[r]??0,a=o[r]??0;if(s!==a)return s-a}return 0}async function ao(t){if(!t.auto_update_check||process.env.ORU_NO_UPDATE_CHECK==="1"||!process.stderr.isTTY)return null;let e=oo(),n=Date.now();if(e&&n-e.lastChecked<ro)return Te(e.latestVersion,W)>0?e.latestVersion:null;let o=await He();return o?(so({lastChecked:n,latestVersion:o}),Te(o,W)>0?o:null):null}function io(t){process.stderr.write(`
46
- Update available: ${W} \u2192 ${t}
45
+ `),be("telemetry_notice_shown","true")}catch(e){process.env.ORU_DEBUG==="1"&&console.error("Telemetry notice failed:",e)}}function io(t,e,n,o,r){let s={cli_version:J,command:t,flags:e,os:process.platform,arch:process.arch,node_version:process.version,is_ci:Pn(),duration_ms:n,exit_code:o};return r!==void 0&&(s.error=r),s}var eo,to,no,Qe=K(()=>{"use strict";kt();de();$e();eo="https://telemetry.oru.sh/v1/events",to=3e3;no=["CI","GITHUB_ACTIONS","GITLAB_CI","CIRCLECI","TRAVIS","JENKINS_URL","BUILDKITE","TF_BUILD"]});var jn={};Ae(jn,{autoBackup:()=>co,performBackup:()=>Ze,shouldAutoBackup:()=>Fn});import De from"fs";import Ne from"path";import Cn from"os";function lo(){return`oru-${new Date().toISOString().replace(/[:.]/g,"-").replace("Z","")}.db`}function Ze(t,e){let n=e.startsWith("~")?Ne.join(Cn.homedir(),e.slice(1)):e;De.mkdirSync(n,{recursive:!0});let o=lo(),r=o.slice(0,-3),s=Ne.join(n,o),a=1;for(;De.existsSync(s);)s=Ne.join(n,`${r}-${a}.db`),a++;return t.exec(`VACUUM INTO '${s.replace(/'/g,"''")}'`),s}function Fn(t,e){let n=t.startsWith("~")?Ne.join(Cn.homedir(),t.slice(1)):t;if(!De.existsSync(n))return!0;let o=De.readdirSync(n).filter(a=>a.startsWith("oru-")&&a.endsWith(".db")).sort();if(o.length===0)return!0;let r=De.statSync(Ne.join(n,o[o.length-1]));return(Date.now()-r.mtimeMs)/1e3/60>=e}function co(t,e,n){try{Fn(e,n)&&Ze(t,e)}catch(o){process.env.ORU_DEBUG==="1"&&console.error("Auto-backup failed:",o)}}var At=K(()=>{"use strict"});async function et(){try{let t=new AbortController,e=setTimeout(()=>t.abort(),1e4),n=await fetch("https://registry.npmjs.org/@tchayen/oru/latest",{signal:t.signal});return clearTimeout(e),n.ok?(await n.json()).version??null:null}catch{return null}}var Lt=K(()=>{"use strict"});var Ct={};Ae(Ct,{checkForUpdate:()=>go,compareVersions:()=>Re,printUpdateNotice:()=>_o});import Mt from"fs";import Pt from"path";import uo from"os";function Un(){let t=process.env.ORU_INSTALL_DIR??Pt.join(uo.homedir(),".oru");return Pt.join(t,".update-state.json")}function po(){try{let t=Mt.readFileSync(Un(),"utf-8");return JSON.parse(t)}catch{return null}}function fo(t){let e=Un();Mt.mkdirSync(Pt.dirname(e),{recursive:!0}),Mt.writeFileSync(e,JSON.stringify(t))}function Re(t,e){let n=t.split(".").map(Number),o=e.split(".").map(Number);for(let r=0;r<3;r++){let s=n[r]??0,a=o[r]??0;if(s!==a)return s-a}return 0}async function go(t){if(!t.auto_update_check||process.env.ORU_NO_UPDATE_CHECK==="1"||!process.stderr.isTTY)return null;let e=po(),n=Date.now();if(e&&n-e.lastChecked<mo)return Re(e.latestVersion,J)>0?e.latestVersion:null;let o=await et();return o?(fo({lastChecked:n,latestVersion:o}),Re(o,J)>0?o:null):null}function _o(t){process.stderr.write(`
46
+ Update available: ${J} \u2192 ${t}
47
47
  Run \`oru self-update\` to upgrade.
48
- `)}var ro,Ve=q(()=>{"use strict";ke();Tt();ro=1440*60*1e3});var Rn={};Ee(Rn,{performUpdate:()=>fo});import{execSync as Dn}from"child_process";import G from"fs";import ue from"path";import xt from"os";function Nn(){let t=process.env.ORU_INSTALL_DIR??ue.join(xt.homedir(),".oru");return ue.join(t,".install-meta")}function lo(){try{let t=G.readFileSync(Nn(),"utf-8"),e={};for(let n of t.split(`
49
- `)){let o=n.indexOf("=");o!==-1&&(e[n.slice(0,o).trim()]=n.slice(o+1).trim())}return e}catch{return null}}function co(){return lo()?.install_method==="script"?"script":"npm"}function uo(){let t=process.platform==="darwin"?"darwin":"linux",e=process.arch==="arm64"?"arm64":"x64";return`${t}-${e}`}async function mo(){process.stderr.write(`Updating via npm...
50
- `),Dn("npm install -g @tchayen/oru@latest",{stdio:"inherit"})}async function po(t){let e=process.env.ORU_INSTALL_DIR??ue.join(xt.homedir(),".oru"),n=ue.join(e,"bin"),o=uo(),r=`https://github.com/tchayen/oru/releases/download/v${t}/oru-v${t}-${o}.tar.gz`;process.stderr.write(`Downloading oru v${t}...
51
- `);let s=new AbortController,a=setTimeout(()=>s.abort(),6e4),u=await fetch(r,{signal:s.signal});if(clearTimeout(a),!u.ok)throw new Error(`Failed to download: ${u.status} from ${r}`);let p=G.mkdtempSync(ue.join(xt.tmpdir(),"oru-update-")),m=ue.join(p,"oru.tar.gz"),d=Buffer.from(await u.arrayBuffer());G.writeFileSync(m,d),G.existsSync(n)&&G.rmSync(n,{recursive:!0}),G.mkdirSync(n,{recursive:!0}),Dn(`tar -xzf "${m}" -C "${n}"`,{stdio:"pipe"}),G.rmSync(p,{recursive:!0});let _=`install_method=script
48
+ `)}var mo,tt=K(()=>{"use strict";$e();Lt();mo=1440*60*1e3});var Wn={};Ae(Wn,{performUpdate:()=>vo});import{execSync as Yn}from"child_process";import z from"fs";import pe from"path";import Ft from"os";function Bn(){let t=process.env.ORU_INSTALL_DIR??pe.join(Ft.homedir(),".oru");return pe.join(t,".install-meta")}function yo(){try{let t=z.readFileSync(Bn(),"utf-8"),e={};for(let n of t.split(`
49
+ `)){let o=n.indexOf("=");o!==-1&&(e[n.slice(0,o).trim()]=n.slice(o+1).trim())}return e}catch{return null}}function bo(){return yo()?.install_method==="script"?"script":"npm"}function ho(){let t=process.platform==="darwin"?"darwin":"linux",e=process.arch==="arm64"?"arm64":"x64";return`${t}-${e}`}async function ko(){process.stderr.write(`Updating via npm...
50
+ `),Yn("npm install -g @tchayen/oru@latest",{stdio:"inherit"})}async function So(t){let e=process.env.ORU_INSTALL_DIR??pe.join(Ft.homedir(),".oru"),n=pe.join(e,"bin"),o=ho(),r=`https://github.com/tchayen/oru/releases/download/v${t}/oru-v${t}-${o}.tar.gz`;process.stderr.write(`Downloading oru v${t}...
51
+ `);let s=new AbortController,a=setTimeout(()=>s.abort(),6e4),u=await fetch(r,{signal:s.signal});if(clearTimeout(a),!u.ok)throw new Error(`Failed to download: ${u.status} from ${r}`);let p=z.mkdtempSync(pe.join(Ft.tmpdir(),"oru-update-")),m=pe.join(p,"oru.tar.gz"),d=Buffer.from(await u.arrayBuffer());z.writeFileSync(m,d),z.existsSync(n)&&z.rmSync(n,{recursive:!0}),z.mkdirSync(n,{recursive:!0}),Yn(`tar -xzf "${m}" -C "${n}"`,{stdio:"pipe"}),z.rmSync(p,{recursive:!0});let _=`install_method=script
52
52
  version=${t}
53
53
  platform=${o}
54
54
  installed_at=${new Date().toISOString()}
55
- `;G.writeFileSync(Nn(),_),process.stderr.write(`Updated to oru v${t}
56
- `)}async function fo(t){let e=await He();if(!e)throw new Error("Failed to fetch latest version from npm registry.");let n=W;if(Te(e,n)<=0){process.stderr.write(`Already up to date (v${n})
55
+ `;z.writeFileSync(Bn(),_),process.stderr.write(`Updated to oru v${t}
56
+ `)}async function vo(t){let e=await et();if(!e)throw new Error("Failed to fetch latest version from npm registry.");let n=J;if(Re(e,n)<=0){process.stderr.write(`Already up to date (v${n})
57
57
  `);return}if(process.stderr.write(`New version available: v${n} \u2192 v${e}
58
- `),t)return;co()==="script"?await po(e):await mo()}var In=q(()=>{"use strict";Ve();Tt();ke()});import{fileURLToPath as Dt}from"url";import $t from"fs";import we from"path";import{spawn as Fn}from"child_process";import{Command as go,Option as z,Help as _o}from"commander";import{sql as nr}from"kysely";import{sql as H}from"kysely";import{randomBytes as Wn}from"crypto";var It="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",Jn=new Set(It),Lt=11;function Ke(t,e){let n=0n;for(let r of t)n=n<<8n|BigInt(r);let o=[];for(let r=0;r<e;r++)o.push(It[Number(n%62n)]),n/=62n;return o.reverse().join("")}function At(t){if(t.length!==Lt)return!1;for(let e of t)if(!Jn.has(e))return!1;return!0}function ne(){return Ke(Wn(8),Lt)}var D=["todo","in_progress","in_review","done"],re=new Set(D),N=["low","medium","high","urgent"],oe=new Set(N);var Mt="todo",Pt="medium";var B=["priority","due","title","created"],Q=class extends Error{prefix;matches;constructor(e,n){super(`Prefix '${e}' is ambiguous, matches: ${n.join(", ")}`),this.name="AmbiguousPrefixError",this.prefix=e,this.matches=n}};function Oe(t,e){try{return JSON.parse(t)}catch{return e}}function Ge(t){return{id:t.id,title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,recurrence:t.recurrence,blocked_by:Oe(t.blocked_by,[]),labels:Oe(t.labels,[]),notes:Oe(t.notes,[]),metadata:Oe(t.metadata,{}),created_at:t.created_at,updated_at:t.updated_at,deleted_at:t.deleted_at}}async function ze(t,e,n){let o=e.id??ne(),r=n??new Date().toISOString(),s={id:o,title:e.title,status:e.status??Mt,priority:e.priority??Pt,owner:e.owner??null,due_at:e.due_at??null,recurrence:e.recurrence??null,blocked_by:e.blocked_by??[],labels:e.labels??[],notes:e.notes??[],metadata:e.metadata??{},created_at:r,updated_at:r,deleted_at:null};return await t.insertInto("tasks").values({id:s.id,title:s.title,status:s.status,priority:s.priority,owner:s.owner,due_at:s.due_at,recurrence:s.recurrence,blocked_by:JSON.stringify(s.blocked_by),labels:JSON.stringify(s.labels),notes:JSON.stringify(s.notes),metadata:JSON.stringify(s.metadata),created_at:s.created_at,updated_at:s.updated_at,deleted_at:s.deleted_at}).execute(),s}async function xe(t,e){let n=t.selectFrom("tasks").selectAll().where("deleted_at","is",null);if(e?.status&&(Array.isArray(e.status)?n=n.where("status","in",e.status):n=n.where("status","=",e.status)),e?.priority&&(Array.isArray(e.priority)?n=n.where("priority","in",e.priority):n=n.where("priority","=",e.priority)),e?.owner&&(n=n.where("owner","=",e.owner)),e?.label){let s=e.label;n=n.where(H`EXISTS (SELECT 1 FROM json_each(labels) WHERE json_each.value = ${s})`)}if(e?.search){let s=e.search.replace(/[\\%_]/g,"\\$&");n=n.where(H`title LIKE '%' || ${s} || '%' ESCAPE '\\' COLLATE NOCASE`)}switch(e?.actionable&&(n=n.where("status","!=","done").where(H`NOT EXISTS (
58
+ `),t)return;bo()==="script"?await So(e):await ko()}var Jn=K(()=>{"use strict";tt();Lt();$e()});import{fileURLToPath as Ut}from"url";import jt from"fs";import Ie from"path";import{spawn as Hn}from"child_process";import{Command as To,Option as Q,Help as wo}from"commander";import{sql as cr}from"kysely";import{sql as V}from"kysely";import{randomBytes as Gn}from"crypto";var Wt="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",zn=new Set(Wt),Jt=11;function ot(t,e){let n=0n;for(let r of t)n=n<<8n|BigInt(r);let o=[];for(let r=0;r<e;r++)o.push(Wt[Number(n%62n)]),n/=62n;return o.reverse().join("")}function Ht(t){if(t.length!==Jt)return!1;for(let e of t)if(!zn.has(e))return!1;return!0}function re(){return ot(Gn(8),Jt)}var $=["todo","in_progress","in_review","done"],oe=new Set($),D=["low","medium","high","urgent"],se=new Set(D),Vt=["title","status","priority","owner","due_at","recurrence","blocked_by","labels","metadata"],st=new Set(Vt);var qt="todo",Xt="medium";var B=["priority","due","title","created"],Z=class extends Error{prefix;matches;constructor(e,n){super(`Prefix '${e}' is ambiguous, matches: ${n.join(", ")}`),this.name="AmbiguousPrefixError",this.prefix=e,this.matches=n}};function Le(t,e){try{return JSON.parse(t)}catch{return e}}function at(t){return{id:t.id,title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,recurrence:t.recurrence,blocked_by:Le(t.blocked_by,[]),labels:Le(t.labels,[]),notes:Le(t.notes,[]),metadata:Le(t.metadata,{}),created_at:t.created_at,updated_at:t.updated_at,deleted_at:t.deleted_at}}async function it(t,e,n){let o=e.id??re(),r=n??new Date().toISOString(),s={id:o,title:e.title,status:e.status??qt,priority:e.priority??Xt,owner:e.owner??null,due_at:e.due_at??null,recurrence:e.recurrence??null,blocked_by:e.blocked_by??[],labels:e.labels??[],notes:e.notes??[],metadata:e.metadata??{},created_at:r,updated_at:r,deleted_at:null};return await t.insertInto("tasks").values({id:s.id,title:s.title,status:s.status,priority:s.priority,owner:s.owner,due_at:s.due_at,recurrence:s.recurrence,blocked_by:JSON.stringify(s.blocked_by),labels:JSON.stringify(s.labels),notes:JSON.stringify(s.notes),metadata:JSON.stringify(s.metadata),created_at:s.created_at,updated_at:s.updated_at,deleted_at:s.deleted_at}).execute(),s}async function Me(t,e){let n=t.selectFrom("tasks").selectAll().where("deleted_at","is",null);if(e?.status&&(Array.isArray(e.status)?n=n.where("status","in",e.status):n=n.where("status","=",e.status)),e?.priority&&(Array.isArray(e.priority)?n=n.where("priority","in",e.priority):n=n.where("priority","=",e.priority)),e?.owner&&(n=n.where("owner","=",e.owner)),e?.label){let s=e.label;n=n.where(V`EXISTS (SELECT 1 FROM json_each(labels) WHERE json_each.value = ${s})`)}if(e?.search){let s=e.search.replace(/[\\%_]/g,"\\$&");n=n.where(V`title LIKE '%' || ${s} || '%' ESCAPE '\\' COLLATE NOCASE`)}switch(e?.actionable&&(n=n.where("status","!=","done").where(V`NOT EXISTS (
59
59
  SELECT 1 FROM json_each(tasks.blocked_by) AS dep
60
60
  JOIN tasks AS blocker ON blocker.id = dep.value
61
61
  WHERE blocker.status != 'done' AND blocker.deleted_at IS NULL
62
- )`)),e?.sql&&(n=n.where(H`(${H.raw(e.sql)})`)),e?.sort??"priority"){case"due":n=n.orderBy(H`CASE WHEN due_at IS NULL THEN 1 ELSE 0 END`,"asc").orderBy("due_at","asc").orderBy("created_at","asc");break;case"title":n=n.orderBy(H`title COLLATE NOCASE`,"asc").orderBy("created_at","asc");break;case"created":n=n.orderBy("created_at","asc");break;default:n=n.orderBy(H`CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`).orderBy("created_at","asc");break}return(e?.limit!==void 0||e?.offset!==void 0)&&(n=n.limit(e.limit??-1)),e?.offset!==void 0&&(n=n.offset(e.offset)),(await n.execute()).map(Ge)}function Ct(t,e){return e.all||e.status!==void 0?t:t.filter(n=>n.status!=="done")}async function I(t,e){let n=await t.selectFrom("tasks").selectAll().where("id","=",e).where("deleted_at","is",null).executeTakeFirst();if(n)return Ge(n);if(!e)return null;let o=e.replace(/[\\%_]/g,"\\$&"),r=await t.selectFrom("tasks").selectAll().where(H`id LIKE ${o} || '%' ESCAPE '\\'`).where("deleted_at","is",null).execute();if(r.length===1)return Ge(r[0]);if(r.length>1)throw new Q(e,r.map(s=>s.id));return null}async function $e(t,e,n,o){let r=await I(t,e);if(!r)return null;let a={updated_at:o??new Date().toISOString()};return n.title!==void 0&&(a.title=n.title),n.status!==void 0&&(a.status=n.status),n.priority!==void 0&&(a.priority=n.priority),n.owner!==void 0&&(a.owner=n.owner),n.due_at!==void 0&&(a.due_at=n.due_at),n.recurrence!==void 0&&(a.recurrence=n.recurrence),n.blocked_by!==void 0&&(a.blocked_by=JSON.stringify(n.blocked_by)),n.labels!==void 0&&(a.labels=JSON.stringify(n.labels)),n.metadata!==void 0&&(a.metadata=JSON.stringify(n.metadata)),await t.updateTable("tasks").set(a).where("id","=",r.id).execute(),I(t,r.id)}async function De(t,e,n,o){let r=await I(t,e);if(!r)return null;let s=n.trim();if(s.length===0||r.notes.some(p=>p.trim()===s))return r;let a=[...r.notes,s],u=o??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(a),updated_at:u}).where("id","=",r.id).execute(),I(t,r.id)}async function Ne(t,e,n,o){let r=await I(t,e);if(!r)return null;let s=o??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(n),updated_at:s}).where("id","=",r.id).execute(),I(t,r.id)}async function Ft(t,e,n){let o=await I(t,e);if(!o)return!1;let r=n??new Date().toISOString(),s=await t.updateTable("tasks").set({deleted_at:r,updated_at:r}).where("id","=",o.id).where("deleted_at","is",null).executeTakeFirst();return BigInt(s.numUpdatedRows)>0n}async function A(t,e,n){let o=ne(),r=n??new Date().toISOString();return await t.insertInto("oplog").values({id:o,task_id:e.task_id,device_id:e.device_id,op_type:e.op_type,field:e.field,value:e.value,timestamp:r}).execute(),{id:o,task_id:e.task_id,device_id:e.device_id,op_type:e.op_type,field:e.field,value:e.value,timestamp:r}}function Hn(){return"NO_COLOR"in process.env?!1:"FORCE_COLOR"in process.env?!0:process.stdout.isTTY??!1}function Re(t,e){let n=`\x1B[${t}m`,o=`\x1B[${e}m`;return r=>Hn()?`${n}${r}${o}`:r}var L=Re(1,22),w=Re(2,22),Qe=Re(3,23),de=Re(37,39);var Vn={MO:"monday",TU:"tuesday",WE:"wednesday",TH:"thursday",FR:"friday",SA:"saturday",SU:"sunday"};function se(t){let e=t,n="";e.startsWith("after:")&&(e=e.slice(6),n=" (after completion)");let o=e.split(";"),r="",s=1,a=null,u=null;for(let d of o){let[_,b]=d.split("=");switch(_){case"FREQ":r=b;break;case"INTERVAL":s=Number(b);break;case"BYDAY":a=b.split(",");break;case"BYMONTHDAY":u=Number(b);break}}if(a&&a.length>0){let d=["MO","TU","WE","TH","FR"];if(a.length===5&&d.every(E=>a.includes(E)))return`weekdays${n}`;let b=a.map(E=>Vn[E]??E.toLowerCase());return`${s>1?`every ${s} weeks on `:"every "}${b.join(", ")}${n}`}if(u!==null){let d=Xn(u);return`${s>1?`every ${s} months on the `:"every "}${d}${n}`}let m={DAILY:"day",WEEKLY:"week",MONTHLY:"month",YEARLY:"year"}[r]??"year";if(s===1)switch(r){case"DAILY":return`daily${n}`;case"WEEKLY":return`weekly${n}`;case"MONTHLY":return`monthly${n}`;case"YEARLY":return`yearly${n}`}return`every ${s} ${m}s${n}`}function Xn(t){let e=["th","st","nd","rd"],n=t%100;return t+(e[(n-20)%10]||e[n]||e[0])}var me={sunday:0,monday:1,tuesday:2,wednesday:3,thursday:4,friday:5,saturday:6};function ae(t,e){let n=e??new Date,o=new Date(Number(t.slice(0,4)),Number(t.slice(5,7))-1,Number(t.slice(8,10)),Number(t.slice(11,13))||0,Number(t.slice(14,16))||0);return t.slice(11,16)==="00:00"&&o.setDate(o.getDate()+1),o<n}function jt(t,e){if(ae(t,e))return!1;let n=e??new Date,o=new Date(Number(t.slice(0,4)),Number(t.slice(5,7))-1,Number(t.slice(8,10)),Number(t.slice(11,13))||0,Number(t.slice(14,16))||0);return t.slice(11,16)==="00:00"&&o.setDate(o.getDate()+1),(o.getTime()-n.getTime())/(1e3*60*60)<=48}function qn(t,e){let n=t.slice(0,10),o=t.slice(11,16),r=o==="00:00"?n:`${n} ${o}`;return ae(t,e)?L(r):r}function Kn(t){let e=t.slice(0,10),n=t.slice(11,16);return n==="00:00"?e:`${e} ${n}`}function Ut(t){switch(t){case"urgent":return L(t);case"low":return w(t);default:return t}}function Gn(t){switch(t){case"done":return w(t);case"in_progress":return L(t);case"in_review":return Qe(t);default:return t}}function zn(t){switch(t){case"done":return w("[x]");case"in_progress":return L("[~]");case"in_review":return de("[r]");default:return w("[ ]")}}function Y(t,e){let n=[];n.push(`${w(t.id)} ${L(t.title)}`);let o=` Status: ${Gn(t.status)} Priority: ${Ut(t.priority)}`;if(t.due_at&&(o+=` Due: ${qn(t.due_at,e)}`),n.push(o),t.recurrence&&n.push(` Recurrence: ${se(t.recurrence)}`),t.owner&&n.push(` Owner: ${t.owner}`),t.blocked_by.length>0&&n.push(` Blocked by: ${t.blocked_by.join(", ")}`),t.labels.length>0&&n.push(` Labels: ${t.labels.join(", ")}`),t.notes.length>0){n.push(` ${w("Notes:")}`);for(let s of t.notes)n.push(` ${w("-")} ${Qe(s)}`)}let r=Object.keys(t.metadata);if(r.length>0){n.push(` ${w("Metadata:")}`);for(let s of r)n.push(` ${w(`${s}:`)} ${String(t.metadata[s])}`)}return n.join(`
63
- `)}function Bt(t){return t.length===0?w("No labels found."):t.join(`
64
- `)}function Ze(t,e){if(t.length===0)return`${w("No tasks found.")}
65
- ${w('Create one with: oru add "Task title"')}`;let n=Math.max(2,...t.map(d=>d.id.length)),o=Math.max(3,...t.map(d=>d.priority.length)),r=Math.max(5,...t.map(d=>(d.owner??"").length)),s=Math.max(3,...t.map(d=>d.due_at?d.due_at.slice(11,16)==="00:00"?10:16:0)),a=Math.max(6,...t.map(d=>(d.labels.length>0?d.labels.join(", "):"").length)),u=Math.max(5,...t.map(d=>d.title.length)),p=w(` ${"ID".padEnd(n)} ${"TITLE".padEnd(u)} ${"PRI".padEnd(o)} ${"OWNER".padEnd(r)} ${"DUE".padEnd(s)} ${"LABELS".padEnd(a)} META`),m=t.map(d=>{let _=zn(d.status),b=d.owner??"",$=d.due_at?Kn(d.due_at):"",E=d.due_at?ae(d.due_at,e):!1,S=d.labels.length>0?d.labels.join(", "):"",O=Object.keys(d.metadata),x=O.length>0?O.map(C=>`${C}=${d.metadata[C]}`).join(", "):"",P=$.padEnd(s),g=E?L(P):P;return`${_} ${w(d.id.padEnd(n))} ${L(d.title.padEnd(u))} ${Ut(d.priority.padEnd(o))} ${b.padEnd(r)} ${g} ${S.padEnd(a)} ${x}`});return[p,...m].join(`
66
- `)}function Yt(t){if(t.length===0)return w("No log entries found.");let e=[];for(let n of t){let o=w(n.timestamp),r=w(`(${n.device_id})`),s;switch(n.op_type){case"create":s=L("CREATE");break;case"delete":s=w("DELETE");break;case"update":s="UPDATE";break}if(n.op_type==="create"){if(e.push(`${o} ${s} ${r}`),n.value)try{let a=JSON.parse(n.value),u=[];for(let[p,m]of Object.entries(a))m!=null&&u.push(`${p} = ${JSON.stringify(m)}`);u.length>0&&e.push(` ${u.join(", ")}`)}catch{e.push(` ${n.value}`)}}else if(n.op_type==="update"){let a=n.field??"";e.push(`${o} ${s} ${a} ${r}`),n.value!==null&&e.push(` ${a} = ${JSON.stringify(n.value)}`)}else e.push(`${o} ${s} ${r}`)}return e.join(`
67
- `)}function Wt(t,e){let n=[["Overdue",t.overdue],["Due Soon",t.due_soon],["In Progress",t.in_progress],["Actionable",t.actionable],["Blocked",t.blocked],["Recently Completed",t.recently_completed]],o={Overdue:"overdue","Due Soon":"due soon","In Progress":"in progress",Actionable:"actionable",Blocked:"blocked","Recently Completed":"recently completed"},r=n.filter(([,p])=>p.length>0);if(r.length===0)return w("Nothing to report.");let s=r.map(([p,m])=>`${L(String(m.length))} ${o[p]}`),u=[w(s.join(", "))];for(let[p,m]of r)if(u.push(`${L(p)} ${w(`(${m.length})`)}`),u.push(Ze(m,e)),p==="Blocked"&&t.blockerTitles){for(let d of m)if(d.blocked_by.length>0){let _=d.blocked_by.map(b=>{let $=t.blockerTitles.get(b);return $?`${b} (${$})`:b}).join(", ");u.push(w(` ${d.id} blocked by: ${_}`))}}return u.join(`
68
-
69
- `)}function et(t,e,n,o="monday"){let r=n??new Date,s=`${r.getFullYear()}-${String(r.getMonth()+1).padStart(2,"0")}-${String(r.getDate()).padStart(2,"0")}`;switch(e){case"today":return t.filter(a=>a.due_at?.slice(0,10)===s);case"this-week":{let a=r.getDay(),u=me[o],p=(a-u+7)%7,m=new Date(r.getFullYear(),r.getMonth(),r.getDate()-p),d=new Date(m.getFullYear(),m.getMonth(),m.getDate()+6),_=`${m.getFullYear()}-${String(m.getMonth()+1).padStart(2,"0")}-${String(m.getDate()).padStart(2,"0")}`,b=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;return t.filter($=>{if(!$.due_at)return!1;let E=$.due_at.slice(0,10);return E>=_&&E<=b})}case"overdue":return t.filter(a=>a.due_at?ae(a.due_at,r):!1)}}var Qn={SU:0,MO:1,TU:2,WE:3,TH:4,FR:5,SA:6};function Zn(t){let e=t.split(";"),n="",o=1,r=null,s=null;for(let a of e){let[u,p]=a.split("=");switch(u){case"FREQ":n=p;break;case"INTERVAL":o=Number(p);break;case"BYDAY":r=p.split(",");break;case"BYMONTHDAY":s=Number(p);break}}return{freq:n,interval:o,byDay:r,byMonthDay:s}}function Ie(t,e){let n=new Date(t);return n.setDate(n.getDate()+e),n}function tt(t,e){let n=new Date(t),o=n.getMonth()+e;return n.setMonth(o),n.getMonth()!==(o%12+12)%12&&n.setDate(0),n}function nt(t,e){let n=Zn(t);switch(n.freq){case"DAILY":return Ie(e,n.interval);case"WEEKLY":{if(n.byDay&&n.byDay.length>0){let o=n.byDay.map(u=>Qn[u]).sort((u,p)=>u-p),r=e.getDay();for(let u of o)if(u>r)return Ie(e,u-r);let s=7-r+o[0],a=(n.interval-1)*7;return Ie(e,s+a)}return Ie(e,n.interval*7)}case"MONTHLY":{if(n.byMonthDay!==null){let o=n.byMonthDay,r=new Date(e);if(e.getDate()<o){if(r.setDate(o),r.getMonth()!==e.getMonth()&&(r=new Date(e.getFullYear(),e.getMonth()+1,0)),r.getTime()<=e.getTime()){r=tt(e,n.interval);let s=r.getMonth();r.setDate(o),r.getMonth()!==s&&(r=new Date(r.getFullYear(),s+1,0))}}else{r=tt(e,n.interval);let s=r.getMonth();r.setDate(o),r.getMonth()!==s&&(r=new Date(r.getFullYear(),s+1,0))}return r}return tt(e,n.interval)}case"YEARLY":{let o=new Date(e);return o.setFullYear(o.getFullYear()+n.interval),o.getMonth()!==e.getMonth()&&o.setDate(0),o}default:throw new Error(`Unsupported FREQ: ${n.freq}`)}}import{createHash as er}from"crypto";var tr="oru-recurrence";function Le(t){let e=er("sha256").update(`${tr}:${t}`).digest();return Ke(e.subarray(0,8),11)}function rt(t){return t===null?null:typeof t=="string"?t:JSON.stringify(t)}function Jt(t){return{title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,recurrence:t.recurrence,blocked_by:t.blocked_by,labels:t.labels,notes:t.notes,metadata:t.metadata}}var Ae=class{constructor(e,n){this.db=e;this.deviceId=n}async add(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r={...e,owner:e.owner||null},s=await ze(n,r,o);return await A(n,{task_id:s.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(Jt(s))},o),s})}async _maybeSpawn(e,n,o){if(n.status!=="done"||!n.recurrence)return null;let r=Le(n.id);if(await I(e,r))return null;let a=n.recurrence,u=a.startsWith("after:");u&&(a=a.slice(6));let p;u?p=new Date(o):n.due_at?p=new Date(n.due_at):p=new Date(o);let m=nt(a,p),d=`${m.getFullYear()}-${String(m.getMonth()+1).padStart(2,"0")}-${String(m.getDate()).padStart(2,"0")}T${String(m.getHours()).padStart(2,"0")}:${String(m.getMinutes()).padStart(2,"0")}:${String(m.getSeconds()).padStart(2,"0")}`,_={id:r,title:n.title,priority:n.priority,owner:n.owner,due_at:d,recurrence:n.recurrence,labels:[...n.labels],metadata:{...n.metadata}},b=await ze(e,_,o);return await A(e,{task_id:b.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(Jt(b))},o),b}async getSpawnedTask(e){let n=Le(e);return I(this.db,n)}async validateBlockedBy(e,n){let o=null;if(e!==null){let s=await I(this.db,e);if(!s)return{valid:!1,error:`Task "${e}" not found.`};o=s.id}let r=[];for(let s of n){let a=await I(this.db,s);if(!a)return{valid:!1,error:`Task "${s}" not found.`};if(o!==null&&a.id===o)return{valid:!1,error:"A task cannot block itself."};r.push(a.id)}if(o!==null&&r.length>0){let s=await xe(this.db),a=new Map(s.map(u=>[u.id,u]));for(let u of r){let p=[u],m=new Set;for(;p.length>0;){let d=p.shift();if(d===o)return{valid:!1,error:`Setting blocked_by to "${u}" would create a circular dependency.`};if(m.has(d))continue;m.add(d);let _=a.get(d);if(_)for(let b of _.blocked_by)m.has(b)||p.push(b)}}}return{valid:!0}}async list(e){return xe(this.db,e)}async get(e){return I(this.db,e)}async update(e,n){return this.db.transaction().execute(async o=>{let r=new Date().toISOString(),s=await $e(o,e,n,r);if(!s)return null;for(let[a,u]of Object.entries(n))a==="note"||u===void 0||await A(o,{task_id:s.id,device_id:this.deviceId,op_type:"update",field:a,value:rt(u)},r);return await this._maybeSpawn(o,s,r),s})}async addNote(e,n){return this.db.transaction().execute(async o=>{let r=new Date().toISOString(),s=await I(o,e);if(!s)return null;let a=n.trim();if(a.length===0||s.notes.some(p=>p.trim()===a))return s;let u=await De(o,s.id,a,r);return await A(o,{task_id:s.id,device_id:this.deviceId,op_type:"update",field:"notes",value:a},r),u})}async updateWithNote(e,n,o){return this.db.transaction().execute(async r=>{let s=new Date().toISOString(),a=await $e(r,e,n,s);if(!a)return null;let u=a.id;for(let[m,d]of Object.entries(n))m==="note"||d===void 0||await A(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:m,value:rt(d)},s);let p=o.trim();return p.length>0&&!a.notes.some(m=>m.trim()===p)&&(a=await De(r,u,p,s),await A(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:"notes",value:p},s)),await this._maybeSpawn(r,a,s),a})}async clearNotes(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r=await Ne(n,e,[],o);return r?(await A(n,{task_id:r.id,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},o),r):null})}async clearNotesAndUpdate(e,n,o){return this.db.transaction().execute(async r=>{let s=new Date().toISOString(),a=await Ne(r,e,[],s);if(!a)return null;let u=a.id;if(await A(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},s),o){let m=o.trim();m.length>0&&(a=await De(r,u,m,s),await A(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:"notes",value:m},s))}if(Object.keys(n).length>0){a=await $e(r,u,n,s);for(let[m,d]of Object.entries(n))m==="note"||d===void 0||await A(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:m,value:rt(d)},s)}return await this._maybeSpawn(r,a,s),a})}async replaceNotes(e,n){return this.db.transaction().execute(async o=>{let r=new Date().toISOString(),s=await Ne(o,e,n,r);if(!s)return null;let a=s.id;await A(o,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},r);for(let u of n)await A(o,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes",value:u},r);return s})}async listLabels(){let e=await xe(this.db),n=new Set;for(let o of e)for(let r of o.labels)n.add(r);return[...n].sort()}async getContext(e){let n=new Date,o=await this.list({sort:"priority",owner:e?.owner,label:e?.label}),r=await this.list({status:"done",sort:"priority",owner:e?.owner,label:e?.label}),s={overdue:[],due_soon:[],in_progress:[],actionable:[],blocked:[],recently_completed:[]},a=new Set(o.filter(d=>d.status!=="done").map(d=>d.id)),u=new Date(n.getTime()-1440*60*1e3).toISOString();for(let d of r)d.updated_at>=u&&s.recently_completed.push(d);for(let d of o){if(d.status==="done")continue;if(d.status==="in_progress"||d.status==="in_review"){s.in_progress.push(d);continue}if(d.due_at&&ae(d.due_at,n)){s.overdue.push(d);continue}if(d.due_at&&jt(d.due_at,n)){s.due_soon.push(d);continue}if(d.blocked_by.some(b=>a.has(b))){s.blocked.push(d);continue}if(d.status==="todo"){s.actionable.push(d);continue}}let p=new Map;for(let d of[...o,...r])p.set(d.id,d.title);s.blockerTitles=p;let m={overdue:s.overdue.length,due_soon:s.due_soon.length,in_progress:s.in_progress.length,actionable:s.actionable.length,blocked:s.blocked.length,recently_completed:s.recently_completed.length};return{sections:s,summary:m}}async log(e){let n=await I(this.db,e);return n?await this.db.selectFrom("oplog").selectAll().where("task_id","=",n.id).orderBy("timestamp","asc").orderBy(nr`rowid`,"asc").execute():null}async delete(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r=await I(n,e);if(!r)return!1;let s=await Ft(n,r.id,o);return s&&await A(n,{task_id:r.id,device_id:this.deviceId,op_type:"delete",field:null,value:null},o),s})}};import{Kysely as rr,SqliteDialect as or}from"kysely";function Ht(t){return new rr({dialect:new or({database:t})})}import sr from"better-sqlite3";import Vt from"path";import ar from"os";import ot from"fs";function ir(){return process.env.ORU_DB_PATH?process.env.ORU_DB_PATH:Vt.join(ar.homedir(),".oru","oru.db")}function Xt(t){let e=t??ir(),n=Vt.dirname(e);ot.existsSync(n)||ot.mkdirSync(n,{recursive:!0,mode:448});let o=new sr(e);o.pragma("journal_mode = WAL"),o.pragma("foreign_keys = ON");try{ot.chmodSync(e,384)}catch{}return o}function lr(t){let e=t.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();return e?parseInt(e.value,10):0}function qt(t,e){let n=lr(t),o=e.filter(a=>a.version>n).sort((a,u)=>a.version-u.version);if(o.length===0)return 0;let r=o[o.length-1].version;return process.stderr.write(`Migrating database from v${n} to v${r}...
70
- `),t.transaction(()=>{for(let a of o)a.up(t),t.prepare("UPDATE meta SET value = ? WHERE key = 'schema_version'").run(String(a.version));return o.length})()}function Kt(t){t.exec(`
62
+ )`)),e?.sql&&(n=n.where(V`(${V.raw(e.sql)})`)),e?.sort??"priority"){case"due":n=n.orderBy(V`CASE WHEN due_at IS NULL THEN 1 ELSE 0 END`,"asc").orderBy("due_at","asc").orderBy("created_at","asc");break;case"title":n=n.orderBy(V`title COLLATE NOCASE`,"asc").orderBy("created_at","asc");break;case"created":n=n.orderBy("created_at","asc");break;default:n=n.orderBy(V`CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`).orderBy("created_at","asc");break}return(e?.limit!==void 0||e?.offset!==void 0)&&(n=n.limit(e.limit??-1)),e?.offset!==void 0&&(n=n.offset(e.offset)),(await n.execute()).map(at)}function Kt(t,e){return e.all||e.status!==void 0?t:t.filter(n=>n.status!=="done")}async function I(t,e){let n=await t.selectFrom("tasks").selectAll().where("id","=",e).where("deleted_at","is",null).executeTakeFirst();if(n)return at(n);if(!e)return null;let o=e.replace(/[\\%_]/g,"\\$&"),r=await t.selectFrom("tasks").selectAll().where(V`id LIKE ${o} || '%' ESCAPE '\\'`).where("deleted_at","is",null).execute();if(r.length===1)return at(r[0]);if(r.length>1)throw new Z(e,r.map(s=>s.id));return null}async function Pe(t,e,n,o){let r=await I(t,e);if(!r)return null;let a={updated_at:o??new Date().toISOString()};return n.title!==void 0&&(a.title=n.title),n.status!==void 0&&(a.status=n.status),n.priority!==void 0&&(a.priority=n.priority),n.owner!==void 0&&(a.owner=n.owner),n.due_at!==void 0&&(a.due_at=n.due_at),n.recurrence!==void 0&&(a.recurrence=n.recurrence),n.blocked_by!==void 0&&(a.blocked_by=JSON.stringify(n.blocked_by)),n.labels!==void 0&&(a.labels=JSON.stringify(n.labels)),n.metadata!==void 0&&(a.metadata=JSON.stringify(n.metadata)),await t.updateTable("tasks").set(a).where("id","=",r.id).execute(),I(t,r.id)}async function Ce(t,e,n,o){let r=await I(t,e);if(!r)return null;let s=n.trim();if(s.length===0||r.notes.some(p=>p.trim()===s))return r;let a=[...r.notes,s],u=o??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(a),updated_at:u}).where("id","=",r.id).execute(),I(t,r.id)}async function Fe(t,e,n,o){let r=await I(t,e);if(!r)return null;let s=o??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(n),updated_at:s}).where("id","=",r.id).execute(),I(t,r.id)}async function Gt(t,e,n){let o=await I(t,e);if(!o)return!1;let r=n??new Date().toISOString(),s=await t.updateTable("tasks").set({deleted_at:r,updated_at:r}).where("id","=",o.id).where("deleted_at","is",null).executeTakeFirst();return BigInt(s.numUpdatedRows)>0n}async function P(t,e,n){let o=re(),r=n??new Date().toISOString();return await t.insertInto("oplog").values({id:o,task_id:e.task_id,device_id:e.device_id,op_type:e.op_type,field:e.field,value:e.value,timestamp:r}).execute(),{id:o,task_id:e.task_id,device_id:e.device_id,op_type:e.op_type,field:e.field,value:e.value,timestamp:r}}function Qn(){return"NO_COLOR"in process.env?!1:"FORCE_COLOR"in process.env?!0:process.stdout.isTTY??!1}function je(t,e){let n=`\x1B[${t}m`,o=`\x1B[${e}m`;return r=>Qn()?`${n}${r}${o}`:r}var A=je(1,22),w=je(2,22),lt=je(3,23),fe=je(37,39);var Zn={MO:"monday",TU:"tuesday",WE:"wednesday",TH:"thursday",FR:"friday",SA:"saturday",SU:"sunday"};function ae(t){let e=t,n="";e.startsWith("after:")&&(e=e.slice(6),n=" (after completion)");let o=e.split(";"),r="",s=1,a=null,u=null;for(let d of o){let[_,b]=d.split("=");switch(_){case"FREQ":r=b;break;case"INTERVAL":s=Number(b);break;case"BYDAY":a=b.split(",");break;case"BYMONTHDAY":u=Number(b);break}}if(a&&a.length>0){let d=["MO","TU","WE","TH","FR"];if(a.length===5&&d.every(E=>a.includes(E)))return`weekdays${n}`;let b=a.map(E=>Zn[E]??E.toLowerCase());return`${s>1?`every ${s} weeks on `:"every "}${b.join(", ")}${n}`}if(u!==null){let d=er(u);return`${s>1?`every ${s} months on the `:"every "}${d}${n}`}let m={DAILY:"day",WEEKLY:"week",MONTHLY:"month",YEARLY:"year"}[r]??"year";if(s===1)switch(r){case"DAILY":return`daily${n}`;case"WEEKLY":return`weekly${n}`;case"MONTHLY":return`monthly${n}`;case"YEARLY":return`yearly${n}`}return`every ${s} ${m}s${n}`}function er(t){let e=["th","st","nd","rd"],n=t%100;return t+(e[(n-20)%10]||e[n]||e[0])}var ge={sunday:0,monday:1,tuesday:2,wednesday:3,thursday:4,friday:5,saturday:6};function ie(t,e){let n=e??new Date,o=new Date(Number(t.slice(0,4)),Number(t.slice(5,7))-1,Number(t.slice(8,10)),Number(t.slice(11,13))||0,Number(t.slice(14,16))||0);return t.slice(11,16)==="00:00"&&o.setDate(o.getDate()+1),o<n}function zt(t,e){if(ie(t,e))return!1;let n=e??new Date,o=new Date(Number(t.slice(0,4)),Number(t.slice(5,7))-1,Number(t.slice(8,10)),Number(t.slice(11,13))||0,Number(t.slice(14,16))||0);return t.slice(11,16)==="00:00"&&o.setDate(o.getDate()+1),(o.getTime()-n.getTime())/(1e3*60*60)<=48}function tr(t,e){let n=t.slice(0,10),o=t.slice(11,16),r=o==="00:00"?n:`${n} ${o}`;return ie(t,e)?A(r):r}function nr(t){let e=t.slice(0,10),n=t.slice(11,16);return n==="00:00"?e:`${e} ${n}`}function Qt(t){switch(t){case"urgent":return A(t);case"low":return w(t);default:return t}}function rr(t){switch(t){case"done":return w(t);case"in_progress":return A(t);case"in_review":return lt(t);default:return t}}function or(t){switch(t){case"done":return w("[x]");case"in_progress":return A("[~]");case"in_review":return fe("[r]");default:return w("[ ]")}}function W(t,e){let n=[];n.push(`${w(t.id)} ${A(t.title)}`);let o=` Status: ${rr(t.status)} Priority: ${Qt(t.priority)}`;if(t.due_at&&(o+=` Due: ${tr(t.due_at,e)}`),n.push(o),t.recurrence&&n.push(` Recurrence: ${ae(t.recurrence)}`),t.owner&&n.push(` Owner: ${t.owner}`),t.blocked_by.length>0&&n.push(` Blocked by: ${t.blocked_by.join(", ")}`),t.labels.length>0&&n.push(` Labels: ${t.labels.join(", ")}`),t.notes.length>0){n.push(` ${w("Notes:")}`);for(let s of t.notes)n.push(` ${w("-")} ${lt(s)}`)}let r=Object.keys(t.metadata);if(r.length>0){n.push(` ${w("Metadata:")}`);for(let s of r)n.push(` ${w(`${s}:`)} ${String(t.metadata[s])}`)}return n.join(`
63
+ `)}function Zt(t){return t.length===0?w("No labels found."):t.join(`
64
+ `)}function ct(t,e){if(t.length===0)return`${w("No tasks found.")}
65
+ ${w('Create one with: oru add "Task title"')}`;let n=Math.max(2,...t.map(d=>d.id.length)),o=Math.max(3,...t.map(d=>d.priority.length)),r=Math.max(5,...t.map(d=>(d.owner??"").length)),s=Math.max(3,...t.map(d=>d.due_at?d.due_at.slice(11,16)==="00:00"?10:16:0)),a=Math.max(6,...t.map(d=>(d.labels.length>0?d.labels.join(", "):"").length)),u=Math.max(5,...t.map(d=>d.title.length)),p=w(` ${"ID".padEnd(n)} ${"TITLE".padEnd(u)} ${"PRI".padEnd(o)} ${"OWNER".padEnd(r)} ${"DUE".padEnd(s)} ${"LABELS".padEnd(a)} META`),m=t.map(d=>{let _=or(d.status),b=d.owner??"",N=d.due_at?nr(d.due_at):"",E=d.due_at?ie(d.due_at,e):!1,S=d.labels.length>0?d.labels.join(", "):"",x=Object.keys(d.metadata),O=x.length>0?x.map(M=>`${M}=${d.metadata[M]}`).join(", "):"",F=N.padEnd(s),g=E?A(F):F;return`${_} ${w(d.id.padEnd(n))} ${A(d.title.padEnd(u))} ${Qt(d.priority.padEnd(o))} ${b.padEnd(r)} ${g} ${S.padEnd(a)} ${O}`});return[p,...m].join(`
66
+ `)}function en(t){if(t.length===0)return w("No log entries found.");let e=[];for(let n of t){let o=w(n.timestamp),r=w(`(${n.device_id})`),s;switch(n.op_type){case"create":s=A("CREATE");break;case"delete":s=w("DELETE");break;case"update":s="UPDATE";break}if(n.op_type==="create"){if(e.push(`${o} ${s} ${r}`),n.value)try{let a=JSON.parse(n.value),u=[];for(let[p,m]of Object.entries(a))m!=null&&u.push(`${p} = ${JSON.stringify(m)}`);u.length>0&&e.push(` ${u.join(", ")}`)}catch{e.push(` ${n.value}`)}}else if(n.op_type==="update"){let a=n.field??"";e.push(`${o} ${s} ${a} ${r}`),n.value!==null&&e.push(` ${a} = ${JSON.stringify(n.value)}`)}else e.push(`${o} ${s} ${r}`)}return e.join(`
67
+ `)}function tn(t,e){let n=[["Overdue",t.overdue],["Due Soon",t.due_soon],["In Progress",t.in_progress],["Actionable",t.actionable],["Blocked",t.blocked],["Recently Completed",t.recently_completed]],o={Overdue:"overdue","Due Soon":"due soon","In Progress":"in progress",Actionable:"actionable",Blocked:"blocked","Recently Completed":"recently completed"},r=n.filter(([,p])=>p.length>0);if(r.length===0)return w("Nothing to report.");let s=r.map(([p,m])=>`${A(String(m.length))} ${o[p]}`),u=[w(s.join(", "))];for(let[p,m]of r)if(u.push(`${A(p)} ${w(`(${m.length})`)}`),u.push(ct(m,e)),p==="Blocked"&&t.blockerTitles){for(let d of m)if(d.blocked_by.length>0){let _=d.blocked_by.map(b=>{let N=t.blockerTitles.get(b);return N?`${b} (${N})`:b}).join(", ");u.push(w(` ${d.id} blocked by: ${_}`))}}return u.join(`
68
+
69
+ `)}function ut(t,e,n,o="monday"){let r=n??new Date,s=`${r.getFullYear()}-${String(r.getMonth()+1).padStart(2,"0")}-${String(r.getDate()).padStart(2,"0")}`;switch(e){case"today":return t.filter(a=>a.due_at?.slice(0,10)===s);case"this-week":{let a=r.getDay(),u=ge[o],p=(a-u+7)%7,m=new Date(r.getFullYear(),r.getMonth(),r.getDate()-p),d=new Date(m.getFullYear(),m.getMonth(),m.getDate()+6),_=`${m.getFullYear()}-${String(m.getMonth()+1).padStart(2,"0")}-${String(m.getDate()).padStart(2,"0")}`,b=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;return t.filter(N=>{if(!N.due_at)return!1;let E=N.due_at.slice(0,10);return E>=_&&E<=b})}case"overdue":return t.filter(a=>a.due_at?ie(a.due_at,r):!1)}}var sr={SU:0,MO:1,TU:2,WE:3,TH:4,FR:5,SA:6};function ar(t){let e=t.split(";"),n="",o=1,r=null,s=null;for(let a of e){let[u,p]=a.split("=");switch(u){case"FREQ":n=p;break;case"INTERVAL":o=Number(p);break;case"BYDAY":r=p.split(",");break;case"BYMONTHDAY":s=Number(p);break}}return{freq:n,interval:o,byDay:r,byMonthDay:s}}function Ue(t,e){let n=new Date(t);return n.setDate(n.getDate()+e),n}function dt(t,e){let n=new Date(t),o=n.getMonth()+e;return n.setMonth(o),n.getMonth()!==(o%12+12)%12&&n.setDate(0),n}function mt(t,e){let n=ar(t);switch(n.freq){case"DAILY":return Ue(e,n.interval);case"WEEKLY":{if(n.byDay&&n.byDay.length>0){let o=n.byDay.map(u=>sr[u]).sort((u,p)=>u-p),r=e.getDay();for(let u of o)if(u>r)return Ue(e,u-r);let s=7-r+o[0],a=(n.interval-1)*7;return Ue(e,s+a)}return Ue(e,n.interval*7)}case"MONTHLY":{if(n.byMonthDay!==null){let o=n.byMonthDay,r=new Date(e);if(e.getDate()<o){if(r.setDate(o),r.getMonth()!==e.getMonth()&&(r=new Date(e.getFullYear(),e.getMonth()+1,0)),r.getTime()<=e.getTime()){r=dt(e,n.interval);let s=r.getMonth();r.setDate(o),r.getMonth()!==s&&(r=new Date(r.getFullYear(),s+1,0))}}else{r=dt(e,n.interval);let s=r.getMonth();r.setDate(o),r.getMonth()!==s&&(r=new Date(r.getFullYear(),s+1,0))}return r}return dt(e,n.interval)}case"YEARLY":{let o=new Date(e);return o.setFullYear(o.getFullYear()+n.interval),o.getMonth()!==e.getMonth()&&o.setDate(0),o}default:throw new Error(`Unsupported FREQ: ${n.freq}`)}}import{createHash as ir}from"crypto";var lr="oru-recurrence";function Ye(t){let e=ir("sha256").update(`${lr}:${t}`).digest();return ot(e.subarray(0,8),11)}function pt(t){return t===null?null:typeof t=="string"?t:JSON.stringify(t)}function nn(t){return{title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,recurrence:t.recurrence,blocked_by:t.blocked_by,labels:t.labels,notes:t.notes,metadata:t.metadata}}var Be=class{constructor(e,n){this.db=e;this.deviceId=n}async add(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r={...e,owner:e.owner||null},s=await it(n,r,o);return await P(n,{task_id:s.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(nn(s))},o),s})}async _maybeSpawn(e,n,o){if(n.status!=="done"||!n.recurrence)return null;let r=Ye(n.id);if(await I(e,r))return null;let a=n.recurrence,u=a.startsWith("after:");u&&(a=a.slice(6));let p;u?p=new Date(o):n.due_at?p=new Date(n.due_at):p=new Date(o);let m=mt(a,p),d=`${m.getFullYear()}-${String(m.getMonth()+1).padStart(2,"0")}-${String(m.getDate()).padStart(2,"0")}T${String(m.getHours()).padStart(2,"0")}:${String(m.getMinutes()).padStart(2,"0")}:${String(m.getSeconds()).padStart(2,"0")}`,_={id:r,title:n.title,priority:n.priority,owner:n.owner,due_at:d,recurrence:n.recurrence,labels:[...n.labels],metadata:{...n.metadata}},b=await it(e,_,o);return await P(e,{task_id:b.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(nn(b))},o),b}async getSpawnedTask(e){let n=Ye(e);return I(this.db,n)}async validateBlockedBy(e,n){let o=null;if(e!==null){let s=await I(this.db,e);if(!s)return{valid:!1,error:`Task "${e}" not found.`};o=s.id}let r=[];for(let s of n){let a=await I(this.db,s);if(!a)return{valid:!1,error:`Task "${s}" not found.`};if(o!==null&&a.id===o)return{valid:!1,error:"A task cannot block itself."};r.push(a.id)}if(o!==null&&r.length>0){let s=await Me(this.db),a=new Map(s.map(u=>[u.id,u]));for(let u of r){let p=[u],m=new Set;for(;p.length>0;){let d=p.shift();if(d===o)return{valid:!1,error:`Setting blocked_by to "${u}" would create a circular dependency.`};if(m.has(d))continue;m.add(d);let _=a.get(d);if(_)for(let b of _.blocked_by)m.has(b)||p.push(b)}}}return{valid:!0}}async list(e){return Me(this.db,e)}async get(e){return I(this.db,e)}async update(e,n){return this.db.transaction().execute(async o=>{let r=new Date().toISOString(),s=await Pe(o,e,n,r);if(!s)return null;for(let[a,u]of Object.entries(n))a==="note"||u===void 0||await P(o,{task_id:s.id,device_id:this.deviceId,op_type:"update",field:a,value:pt(u)},r);return await this._maybeSpawn(o,s,r),s})}async addNote(e,n){return this.db.transaction().execute(async o=>{let r=new Date().toISOString(),s=await I(o,e);if(!s)return null;let a=n.trim();if(a.length===0||s.notes.some(p=>p.trim()===a))return s;let u=await Ce(o,s.id,a,r);return await P(o,{task_id:s.id,device_id:this.deviceId,op_type:"update",field:"notes",value:a},r),u})}async updateWithNote(e,n,o){return this.db.transaction().execute(async r=>{let s=new Date().toISOString(),a=await Pe(r,e,n,s);if(!a)return null;let u=a.id;for(let[m,d]of Object.entries(n))m==="note"||d===void 0||await P(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:m,value:pt(d)},s);let p=o.trim();return p.length>0&&!a.notes.some(m=>m.trim()===p)&&(a=await Ce(r,u,p,s),await P(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:"notes",value:p},s)),await this._maybeSpawn(r,a,s),a})}async clearNotes(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r=await Fe(n,e,[],o);return r?(await P(n,{task_id:r.id,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},o),r):null})}async clearNotesAndUpdate(e,n,o){return this.db.transaction().execute(async r=>{let s=new Date().toISOString(),a=await Fe(r,e,[],s);if(!a)return null;let u=a.id;if(await P(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},s),o){let m=o.trim();m.length>0&&(a=await Ce(r,u,m,s),await P(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:"notes",value:m},s))}if(Object.keys(n).length>0){a=await Pe(r,u,n,s);for(let[m,d]of Object.entries(n))m==="note"||d===void 0||await P(r,{task_id:u,device_id:this.deviceId,op_type:"update",field:m,value:pt(d)},s)}return await this._maybeSpawn(r,a,s),a})}async replaceNotes(e,n){return this.db.transaction().execute(async o=>{let r=new Date().toISOString(),s=await Fe(o,e,n,r);if(!s)return null;let a=s.id;await P(o,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},r);for(let u of n)await P(o,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes",value:u},r);return s})}async listLabels(){let e=await Me(this.db),n=new Set;for(let o of e)for(let r of o.labels)n.add(r);return[...n].sort()}async getContext(e){let n=new Date,o=await this.list({sort:"priority",owner:e?.owner,label:e?.label}),r=await this.list({status:"done",sort:"priority",owner:e?.owner,label:e?.label}),s={overdue:[],due_soon:[],in_progress:[],actionable:[],blocked:[],recently_completed:[]},a=new Set(o.filter(d=>d.status!=="done").map(d=>d.id)),u=new Date(n.getTime()-1440*60*1e3).toISOString();for(let d of r)d.updated_at>=u&&s.recently_completed.push(d);for(let d of o){if(d.status==="done")continue;if(d.status==="in_progress"||d.status==="in_review"){s.in_progress.push(d);continue}if(d.due_at&&ie(d.due_at,n)){s.overdue.push(d);continue}if(d.due_at&&zt(d.due_at,n)){s.due_soon.push(d);continue}if(d.blocked_by.some(b=>a.has(b))){s.blocked.push(d);continue}if(d.status==="todo"){s.actionable.push(d);continue}}let p=new Map;for(let d of[...o,...r])p.set(d.id,d.title);s.blockerTitles=p;let m={overdue:s.overdue.length,due_soon:s.due_soon.length,in_progress:s.in_progress.length,actionable:s.actionable.length,blocked:s.blocked.length,recently_completed:s.recently_completed.length};return{sections:s,summary:m}}async log(e){let n=await I(this.db,e);return n?await this.db.selectFrom("oplog").selectAll().where("task_id","=",n.id).orderBy("timestamp","asc").orderBy(cr`rowid`,"asc").execute():null}async delete(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r=await I(n,e);if(!r)return!1;let s=await Gt(n,r.id,o);return s&&await P(n,{task_id:r.id,device_id:this.deviceId,op_type:"delete",field:null,value:null},o),s})}};import{Kysely as ur,SqliteDialect as dr}from"kysely";function rn(t){return new ur({dialect:new dr({database:t})})}import mr from"better-sqlite3";import on from"path";import pr from"os";import ft from"fs";function fr(){return process.env.ORU_DB_PATH?process.env.ORU_DB_PATH:on.join(pr.homedir(),".oru","oru.db")}function sn(t){let e=t??fr(),n=on.dirname(e);ft.existsSync(n)||ft.mkdirSync(n,{recursive:!0,mode:448});let o=new mr(e);o.pragma("journal_mode = WAL"),o.pragma("foreign_keys = ON");try{ft.chmodSync(e,384)}catch{}return o}function gr(t){let e=t.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();return e?parseInt(e.value,10):0}function an(t,e){let n=gr(t),o=e.filter(a=>a.version>n).sort((a,u)=>a.version-u.version);if(o.length===0)return 0;let r=o[o.length-1].version;return process.stderr.write(`Migrating database from v${n} to v${r}...
70
+ `),t.transaction(()=>{for(let a of o)a.up(t),t.prepare("UPDATE meta SET value = ? WHERE key = 'schema_version'").run(String(a.version));return o.length})()}function ln(t){t.exec(`
71
71
  CREATE TABLE IF NOT EXISTS tasks (
72
72
  id TEXT PRIMARY KEY,
73
73
  title TEXT NOT NULL,
@@ -97,7 +97,7 @@ ${w('Create one with: oru add "Task title"')}`;let n=Math.max(2,...t.map(d=>d.id
97
97
  );
98
98
 
99
99
  INSERT OR IGNORE INTO meta (key, value) VALUES ('schema_version', '1');
100
- `),qt(t,cr)}var cr=[{version:2,up:t=>{t.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_id ON oplog(task_id)"),t.exec("CREATE INDEX IF NOT EXISTS idx_oplog_device_id ON oplog(device_id)")}},{version:3,up:t=>{t.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_timestamp ON oplog(task_id, timestamp, id)")}},{version:4,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN due_at TEXT")}},{version:5,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN blocked_by TEXT NOT NULL DEFAULT '[]'")}},{version:6,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN owner TEXT")}},{version:7,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN recurrence TEXT")}}];function Gt(t){let{deleted_at:e,...n}=t;return n}function zt(t){let e={};for(let[o,r]of Object.entries(t))o!=="blockerTitles"&&(e[o]=r.length);let n={summary:e};for(let[o,r]of Object.entries(t))o!=="blockerTitles"&&Array.isArray(r)&&r.length>0&&(n[o]=r.map(Gt));if(t.blockerTitles&&t.blocked.length>0){let o=new Set(t.blocked.flatMap(s=>s.blocked_by)),r={};for(let s of o){let a=t.blockerTitles.get(s);a&&(r[s]=a)}Object.keys(r).length>0&&(n.blocker_titles=r)}return JSON.stringify(n,null,2)}function V(t){let{deleted_at:e,...n}=t;return JSON.stringify(n,null,2)}function Qt(t){return JSON.stringify(t.map(Gt),null,2)}function Zt(t){return JSON.stringify(t,null,2)}function en(t){return JSON.stringify(t,null,2)}import ur from"better-sqlite3";import tn from"fs";import dr from"path";var Me=class{db;constructor(e){tn.mkdirSync(dr.dirname(e),{recursive:!0}),this.db=new ur(e),this.db.pragma("journal_mode = WAL"),this.db.pragma("busy_timeout = 5000"),this.db.exec(`
100
+ `),an(t,_r)}var _r=[{version:2,up:t=>{t.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_id ON oplog(task_id)"),t.exec("CREATE INDEX IF NOT EXISTS idx_oplog_device_id ON oplog(device_id)")}},{version:3,up:t=>{t.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_timestamp ON oplog(task_id, timestamp, id)")}},{version:4,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN due_at TEXT")}},{version:5,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN blocked_by TEXT NOT NULL DEFAULT '[]'")}},{version:6,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN owner TEXT")}},{version:7,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN recurrence TEXT")}}];function cn(t){let{deleted_at:e,...n}=t;return n}function un(t){let e={};for(let[o,r]of Object.entries(t))o!=="blockerTitles"&&(e[o]=r.length);let n={summary:e};for(let[o,r]of Object.entries(t))o!=="blockerTitles"&&Array.isArray(r)&&r.length>0&&(n[o]=r.map(cn));if(t.blockerTitles&&t.blocked.length>0){let o=new Set(t.blocked.flatMap(s=>s.blocked_by)),r={};for(let s of o){let a=t.blockerTitles.get(s);a&&(r[s]=a)}Object.keys(r).length>0&&(n.blocker_titles=r)}return JSON.stringify(n,null,2)}function q(t){let{deleted_at:e,...n}=t;return JSON.stringify(n,null,2)}function dn(t){return JSON.stringify(t.map(cn),null,2)}function mn(t){return JSON.stringify(t,null,2)}function pn(t){return JSON.stringify(t,null,2)}import yr from"better-sqlite3";import fn from"fs";import br from"path";var We=class{db;constructor(e){fn.mkdirSync(br.dirname(e),{recursive:!0}),this.db=new yr(e),this.db.pragma("journal_mode = WAL"),this.db.pragma("busy_timeout = 5000"),this.db.exec(`
101
101
  CREATE TABLE IF NOT EXISTS oplog (
102
102
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
103
103
  id TEXT UNIQUE NOT NULL,
@@ -108,9 +108,9 @@ ${w('Create one with: oru add "Task title"')}`;let n=Math.max(2,...t.map(d=>d.id
108
108
  value TEXT,
109
109
  timestamp TEXT NOT NULL
110
110
  );
111
- `);try{tn.chmodSync(e,384)}catch{}}async push(e){let n=this.db.prepare(`INSERT OR IGNORE INTO oplog (id, task_id, device_id, op_type, field, value, timestamp)
112
- VALUES (?, ?, ?, ?, ?, ?, ?)`);this.db.transaction(r=>{for(let s of r)n.run(s.id,s.task_id,s.device_id,s.op_type,s.field,s.value,s.timestamp)})(e)}async pull(e){let n=e?parseInt(e,10):0,o=Number.isNaN(n)?0:n,r=this.db.prepare("SELECT seq, id, task_id, device_id, op_type, field, value, timestamp FROM oplog WHERE seq > ? ORDER BY seq ASC LIMIT 10000").all(o);if(r.length===0)return{entries:[],cursor:e};let s=r[r.length-1].seq;return{entries:r.map(({seq:u,...p})=>p),cursor:String(s)}}close(){this.db.close()}};import at from"fs";import _r from"path";import yr from"os";var mr=new Set(["create","update","delete"]),pr=1e3;function st(t){try{return JSON.parse(t),!0}catch{return!1}}function pe(t){return t.filter(e=>typeof e=="string")}function nn(t,e){t.transaction(()=>{let n=t.prepare(`INSERT OR IGNORE INTO oplog (id, task_id, device_id, op_type, field, value, timestamp)
113
- VALUES (?, ?, ?, ?, ?, ?, ?)`);for(let r of e)mr.has(r.op_type)&&n.run(r.id,r.task_id,r.device_id,r.op_type,r.field,r.value,r.timestamp);let o=[...new Set(e.map(r=>r.task_id))];for(let r of o)fr(t,r)})()}function fr(t,e){let n=t.prepare("SELECT * FROM oplog WHERE task_id = ? ORDER BY timestamp ASC, CASE WHEN field = 'notes_clear' THEN 0 ELSE 1 END ASC, id ASC").all(e);if(n.length===0)return;let o=n.find(g=>g.op_type==="create");if(!o||o.value===null||o.value===void 0)return;let r;try{r=JSON.parse(o.value)}catch{return}let s=typeof r.title=="string"?r.title:"Untitled",a=re.has(r.status)?r.status:"todo",u=oe.has(r.priority)?r.priority:"medium",p=typeof r.owner=="string"&&r.owner.trim().length>0?r.owner:null,m=typeof r.due_at=="string"?r.due_at:null,d=typeof r.recurrence=="string"?r.recurrence:null,_=JSON.stringify(Array.isArray(r.blocked_by)?pe(r.blocked_by):[]),b=JSON.stringify(Array.isArray(r.labels)?pe(r.labels):[]),$=JSON.stringify(r.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:{}),E=[...Array.isArray(r.notes)?pe(r.notes):[]],S=null,O=o.timestamp,x=null;for(let g of n)g.op_type==="update"&&(!x||g.timestamp>x)&&(x=g.timestamp);let P={};for(let g of n)if(g.op_type!=="create"){if(g.op_type==="delete"){x!==null&&x>=g.timestamp||(S=g.timestamp,g.timestamp>O&&(O=g.timestamp));continue}if(g.op_type==="update"){let C=g.field;if(!C)continue;if(C==="notes_clear"){E.length=0,g.timestamp>O&&(O=g.timestamp),S&&g.timestamp>=S&&(S=null);continue}if(C==="notes"){if(g.value&&g.value.trim().length>0){let R=g.value.trim();E.length<pr&&!E.some(c=>c.trim()===R)&&E.push(R)}g.timestamp>O&&(O=g.timestamp),S&&g.timestamp>=S&&(S=null);continue}let J=P[C];if(J&&(g.timestamp<J.timestamp||g.timestamp===J.timestamp&&g.id<J.id))continue;let F=!1;switch(C){case"title":typeof g.value=="string"&&(s=g.value,F=!0);break;case"status":g.value&&re.has(g.value)&&(a=g.value,F=!0);break;case"priority":g.value&&oe.has(g.value)&&(u=g.value,F=!0);break;case"owner":p=g.value&&g.value.trim().length>0?g.value:null,F=!0;break;case"due_at":m=g.value&&g.value.trim().length>0?g.value:null,F=!0;break;case"blocked_by":if(g.value&&st(g.value)){let R=JSON.parse(g.value);Array.isArray(R)&&(_=JSON.stringify(pe(R)),F=!0)}break;case"labels":if(g.value&&st(g.value)){let R=JSON.parse(g.value);Array.isArray(R)&&(b=JSON.stringify(pe(R)),F=!0)}break;case"metadata":if(g.value&&st(g.value)){let R=JSON.parse(g.value);typeof R=="object"&&R!==null&&!Array.isArray(R)&&($=g.value,F=!0)}break;case"recurrence":d=g.value&&g.value.trim().length>0?g.value:null,F=!0;break}F&&(P[C]={timestamp:g.timestamp,id:g.id}),g.timestamp>O&&(O=g.timestamp),S&&g.timestamp>=S&&(S=null)}}t.prepare(`INSERT INTO tasks (id, title, status, priority, owner, due_at, recurrence, blocked_by, labels, notes, metadata, created_at, updated_at, deleted_at)
111
+ `);try{fn.chmodSync(e,384)}catch{}}async push(e){let n=this.db.prepare(`INSERT OR IGNORE INTO oplog (id, task_id, device_id, op_type, field, value, timestamp)
112
+ VALUES (?, ?, ?, ?, ?, ?, ?)`);this.db.transaction(r=>{for(let s of r)n.run(s.id,s.task_id,s.device_id,s.op_type,s.field,s.value,s.timestamp)})(e)}async pull(e){let n=e?parseInt(e,10):0,o=Number.isNaN(n)?0:n,r=this.db.prepare("SELECT seq, id, task_id, device_id, op_type, field, value, timestamp FROM oplog WHERE seq > ? ORDER BY seq ASC LIMIT 10000").all(o);if(r.length===0)return{entries:[],cursor:e};let s=r[r.length-1].seq;return{entries:r.map(({seq:u,...p})=>p),cursor:String(s)}}close(){this.db.close()}};import _t from"fs";import wr from"path";import Er from"os";var hr=new Set(["create","update","delete"]),kr=1e3;function Sr(t){return st.has(t)}function gt(t){try{return JSON.parse(t),!0}catch{return!1}}function _e(t){return t.filter(e=>typeof e=="string")}function gn(t,e){t.transaction(()=>{let n=t.prepare(`INSERT OR IGNORE INTO oplog (id, task_id, device_id, op_type, field, value, timestamp)
113
+ VALUES (?, ?, ?, ?, ?, ?, ?)`);for(let r of e)hr.has(r.op_type)&&n.run(r.id,r.task_id,r.device_id,r.op_type,r.field,r.value,r.timestamp);let o=[...new Set(e.map(r=>r.task_id))];for(let r of o)vr(t,r)})()}function vr(t,e){let n=t.prepare("SELECT * FROM oplog WHERE task_id = ? ORDER BY timestamp ASC, CASE WHEN field = 'notes_clear' THEN 0 ELSE 1 END ASC, id ASC").all(e);if(n.length===0)return;let o=n.find(g=>g.op_type==="create");if(!o||o.value===null||o.value===void 0)return;let r;try{r=JSON.parse(o.value)}catch{return}let s=typeof r.title=="string"?r.title:"Untitled",a=oe.has(r.status)?r.status:"todo",u=se.has(r.priority)?r.priority:"medium",p=typeof r.owner=="string"&&r.owner.trim().length>0?r.owner:null,m=typeof r.due_at=="string"?r.due_at:null,d=typeof r.recurrence=="string"?r.recurrence:null,_=JSON.stringify(Array.isArray(r.blocked_by)?_e(r.blocked_by):[]),b=JSON.stringify(Array.isArray(r.labels)?_e(r.labels):[]),N=JSON.stringify(r.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:{}),E=[...Array.isArray(r.notes)?_e(r.notes):[]],S=null,x=o.timestamp,O=null;for(let g of n)g.op_type==="update"&&(!O||g.timestamp>O)&&(O=g.timestamp);let F={};for(let g of n)if(g.op_type!=="create"){if(g.op_type==="delete"){O!==null&&O>=g.timestamp||(S=g.timestamp,g.timestamp>x&&(x=g.timestamp));continue}if(g.op_type==="update"){let M=g.field;if(!M)continue;if(M==="notes_clear"){E.length=0,g.timestamp>x&&(x=g.timestamp),S&&g.timestamp>=S&&(S=null);continue}if(M==="notes"){if(g.value&&g.value.trim().length>0){let R=g.value.trim();E.length<kr&&!E.some(c=>c.trim()===R)&&E.push(R)}g.timestamp>x&&(x=g.timestamp),S&&g.timestamp>=S&&(S=null);continue}if(!Sr(M))continue;let H=F[M];if(H&&(g.timestamp<H.timestamp||g.timestamp===H.timestamp&&g.id<H.id))continue;let j=!1;switch(M){case"title":typeof g.value=="string"&&(s=g.value,j=!0);break;case"status":g.value&&oe.has(g.value)&&(a=g.value,j=!0);break;case"priority":g.value&&se.has(g.value)&&(u=g.value,j=!0);break;case"owner":p=g.value&&g.value.trim().length>0?g.value:null,j=!0;break;case"due_at":m=g.value&&g.value.trim().length>0?g.value:null,j=!0;break;case"blocked_by":if(g.value&&gt(g.value)){let R=JSON.parse(g.value);Array.isArray(R)&&(_=JSON.stringify(_e(R)),j=!0)}break;case"labels":if(g.value&&gt(g.value)){let R=JSON.parse(g.value);Array.isArray(R)&&(b=JSON.stringify(_e(R)),j=!0)}break;case"metadata":if(g.value&&gt(g.value)){let R=JSON.parse(g.value);typeof R=="object"&&R!==null&&!Array.isArray(R)&&(N=g.value,j=!0)}break;case"recurrence":d=g.value&&g.value.trim().length>0?g.value:null,j=!0;break}j&&(F[M]={timestamp:g.timestamp,id:g.id}),g.timestamp>x&&(x=g.timestamp),S&&g.timestamp>=S&&(S=null)}}t.prepare(`INSERT INTO tasks (id, title, status, priority, owner, due_at, recurrence, blocked_by, labels, notes, metadata, created_at, updated_at, deleted_at)
114
114
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
115
115
  ON CONFLICT(id) DO UPDATE SET
116
116
  title = excluded.title,
@@ -124,19 +124,19 @@ ${w('Create one with: oru add "Task title"')}`;let n=Math.max(2,...t.map(d=>d.id
124
124
  notes = excluded.notes,
125
125
  metadata = excluded.metadata,
126
126
  updated_at = excluded.updated_at,
127
- deleted_at = excluded.deleted_at`).run(e,s,a,u,p,m,d,_,b,JSON.stringify(E),$,o.timestamp,O,S)}var gr=1e3,Pe=class{constructor(e,n,o,r=gr){this.db=e;this.remote=n;this.deviceId=o;this.maxPullIterations=r}async push(){let e=this.db.prepare("SELECT value FROM meta WHERE key = ?").get(`push_hwm_${this.deviceId}`),n=e&&parseInt(e.value,10)||0,o=this.db.prepare("SELECT rowid, * FROM oplog WHERE device_id = ? AND rowid > ? ORDER BY rowid ASC").all(this.deviceId,n);if(o.length===0)return 0;await this.remote.push(o);let r=o[o.length-1].rowid;return this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
128
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(`push_hwm_${this.deviceId}`,String(r)),o.length}async pull(){let e=0,n=0;for(;;){if(++n>this.maxPullIterations)throw new Error(`Sync pull loop exceeded ${this.maxPullIterations} iterations - aborting to prevent infinite loop. This likely indicates a bug in the remote backend.`);let r=this.db.prepare("SELECT value FROM meta WHERE key = ?").get(`pull_cursor_${this.deviceId}`)?.value??null,s=await this.remote.pull(r);if(s.entries.length===0)break;nn(this.db,s.entries),s.cursor&&this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
129
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(`pull_cursor_${this.deviceId}`,s.cursor);let a=s.entries.filter(u=>u.device_id!==this.deviceId);if(e+=a.length,s.cursor===r)break}return e}async sync(){let e=await this.push(),n=await this.pull();return{pushed:e,pulled:n}}};async function rn(t,e,n){let o=t.name,r=_r.join(yr.tmpdir(),`oru-sync-backup-${Date.now()}.db`);t.exec(`VACUUM INTO '${r.replace(/'/g,"''")}'`);try{return await new Pe(t,e,n).sync()}catch(s){t.close();for(let a of["-wal","-shm"])try{at.unlinkSync(o+a)}catch{}throw at.copyFileSync(r,o),s}finally{e.close?.();try{at.unlinkSync(r)}catch{}}}function on(t){let e=t.prepare("SELECT value FROM meta WHERE key = 'device_id'").get();if(e)return e.value;let n=ne();return t.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('device_id', ?)").run(n),t.prepare("SELECT value FROM meta WHERE key = 'device_id'").get().value}ut();var wr={sunday:0,sun:0,monday:1,mon:1,tuesday:2,tue:2,wednesday:3,wed:3,thursday:4,thu:4,friday:5,fri:5,saturday:6,sat:6},an={january:1,jan:1,february:2,feb:2,march:3,mar:3,april:4,apr:4,may:5,june:6,jun:6,july:7,jul:7,august:8,aug:8,september:9,sep:9,sept:9,october:10,oct:10,november:11,nov:11,december:12,dec:12};function dt(t,e="mdy",n="monday",o="same_day",r){let s=r??new Date,a=t.trim();if(!a)return null;let{datePart:u,timePart:p}=Er(a),m=xr(u,e,n,o,s);if(!m)return null;if(p){let d=$r(p);if(!d)return null;m.setHours(d.hours,d.minutes,0,0)}return Dr(m)}function Er(t){let e=t.match(/^(\d{4}-\d{2}-\d{2})T(\d{1,2}:\d{2})$/i);if(e)return{datePart:e[1],timePart:e[2]};let n=t.split(/\s+/);if(n.length===1)return{datePart:n[0],timePart:null};let o=n[n.length-1];return Or(o)?{datePart:n.slice(0,-1).join(" "),timePart:o}:{datePart:t,timePart:null}}function Or(t){return/^\d{1,2}:\d{2}\s*(am?|pm?)?$/i.test(t)||/^\d{1,2}\s*(am?|pm?)$/i.test(t)}function xr(t,e,n,o,r){let s=t.toLowerCase().trim();if(s==="today"||s==="tod")return Z(r);if(s==="tomorrow"||s==="tom"){let m=Z(r);return m.setDate(m.getDate()+1),m}if(s==="tonight"){let m=Z(r);return m.setHours(18,0,0,0),m}if(s==="next week")return dn(r,me[n]);if(s==="next month"){if(o==="first")return new Date(r.getFullYear(),r.getMonth()+1,1);let m=(r.getMonth()+1)%12,d=new Date(r.getFullYear(),r.getMonth()+1,r.getDate());return d.getMonth()!==m?new Date(r.getFullYear(),r.getMonth()+2,0):d}if(s==="end of month")return new Date(r.getFullYear(),r.getMonth()+1,0);if(s==="end of week"){let m=(me[n]+6)%7,d=Z(r),_=d.getDay(),b=(m-_+7)%7;return b===0||d.setDate(d.getDate()+b),d}{let m=s;m.startsWith("next ")&&(m=m.slice(5));let d=wr[m];if(d!==void 0)return dn(r,d)}{let m=s.match(/^in\s+(\d+)\s+(days?|weeks?|months?)$/);if(m){let d=Number(m[1]),_=m[2],b=Z(r);if(_.startsWith("day"))b.setDate(b.getDate()+d);else if(_.startsWith("week"))b.setDate(b.getDate()+d*7);else if(_.startsWith("month")){let $=b.getDate();b.setDate(1),b.setMonth(b.getMonth()+d);let E=new Date(b.getFullYear(),b.getMonth()+1,0).getDate();b.setDate(Math.min($,E))}return b}}{let m=s.match(/^([a-z]+)\s+(\d+)(?:st|nd|rd|th)?$/);if(m){let d=an[m[1]];if(d!==void 0){let _=Number(m[2]);return cn(r,d,_)}}}{let m=s.match(/^(\d+)(?:st|nd|rd|th)?\s+([a-z]+)$/);if(m){let d=an[m[2]];if(d!==void 0){let _=Number(m[1]);return cn(r,d,_)}}}let a=t.match(/^(\d{4})-(\d{2})-(\d{2})$/);if(a)return ee(Number(a[1]),Number(a[2]),Number(a[3]));let u=t.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);if(u){let m=Number(u[1]),d=Number(u[2]),_=Number(u[3]);return ln(m,d,_,e)}let p=t.match(/^(\d{1,2})\/(\d{1,2})$/);if(p){let m=Number(p[1]),d=Number(p[2]);return ln(m,d,r.getFullYear(),e)}return null}function ln(t,e,n,o){if(o==="dmy"){let s=ee(n,e,t);return s||ee(n,t,e)}let r=ee(n,t,e);return r||ee(n,e,t)}function ee(t,e,n){if(e<1||e>12||n<1||n>31)return null;let o=new Date(t,e-1,n);return o.getFullYear()!==t||o.getMonth()!==e-1||o.getDate()!==n?null:o}function cn(t,e,n){let o=t.getFullYear(),r=ee(o,e,n);return r?r<Z(t)?ee(o+1,e,n):r:null}function $r(t){let e=t.trim().toLowerCase(),n=e.match(/^(\d{1,2}):(\d{2})\s*(am?|pm?)?$/);if(n){let r=Number(n[1]),s=Number(n[2]),a=n[3];return a&&(r=un(r,a)),r<0||r>23||s<0||s>59?null:{hours:r,minutes:s}}let o=e.match(/^(\d{1,2})\s*(am?|pm?)$/);if(o){let r=Number(o[1]);return r=un(r,o[2]),r<0||r>23?null:{hours:r,minutes:0}}return null}function un(t,e){let n=e.startsWith("p");return n&&t<12?t+12:!n&&t===12?0:t}function Dr(t){let e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),o=String(t.getDate()).padStart(2,"0"),r=String(t.getHours()).padStart(2,"0"),s=String(t.getMinutes()).padStart(2,"0"),a=String(t.getSeconds()).padStart(2,"0");return`${e}-${n}-${o}T${r}:${s}:${a}`}function Z(t){return new Date(t.getFullYear(),t.getMonth(),t.getDate())}function dn(t,e){let n=Z(t),o=n.getDay(),r=e-o;return r<=0&&(r+=7),n.setDate(n.getDate()+r),n}import mt from"fs";import Ir from"path";import Lr from"os";import{spawn as Ar}from"child_process";import{stringify as Mr,parse as Pr}from"smol-toml";var Nr=new Set(["DAILY","WEEKLY","MONTHLY","YEARLY"]),Rr=new Set(["MO","TU","WE","TH","FR","SA","SU"]);function _e(t){if(!t||t.trim().length===0)return!1;let e=t;if(e.startsWith("after:")&&(e=e.slice(6)),!e.startsWith("FREQ="))return!1;let n=e.split(";"),o=!1;for(let r of n){let[s,a]=r.split("=");if(!s||a===void 0)return!1;switch(s){case"FREQ":if(!Nr.has(a))return!1;o=!0;break;case"INTERVAL":{let u=Number(a);if(!Number.isInteger(u)||u<1)return!1;break}case"BYDAY":for(let u of a.split(","))if(!Rr.has(u))return!1;break;case"BYMONTHDAY":{let u=Number(a);if(!Number.isInteger(u)||u<1||u>31)return!1;break}default:return!1}}return o}var mn=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/;function Ce(t){return t.replace(/[\r\n]+/g," ").trim()}function pn(t,{required:e=!1}={}){return t.length===0?{valid:!1,message:e?"Title is required.":"Title cannot be empty."}:t.length>1e3?{valid:!1,message:"Title exceeds maximum length of 1000 characters."}:{valid:!0}}function fn(t){return t.length>1e4?{valid:!1,message:"Note exceeds maximum length of 10000 characters."}:{valid:!0}}function gn(t){for(let e of t){if(e.length===0)return{valid:!1,message:"Label cannot be empty."};if(e.length>200)return{valid:!1,message:"Label exceeds maximum length of 200 characters."}}return{valid:!0}}function _n(t){let e={title:t.title,status:t.status,priority:t.priority};t.owner&&(e.owner=t.owner),t.due_at&&(e.due=t.due_at),t.recurrence&&(e.recurrence=t.recurrence),e.blocked_by=t.blocked_by,e.labels=t.labels,Object.keys(t.metadata).length>0&&(e.metadata=t.metadata);let n=`+++
130
- `;if(n+=Mr(e),n+=`
127
+ deleted_at = excluded.deleted_at`).run(e,s,a,u,p,m,d,_,b,JSON.stringify(E),N,o.timestamp,x,S)}var Tr=1e3,Je=class{constructor(e,n,o,r=Tr){this.db=e;this.remote=n;this.deviceId=o;this.maxPullIterations=r}async push(){let e=this.db.prepare("SELECT value FROM meta WHERE key = ?").get(`push_hwm_${this.deviceId}`),n=e&&parseInt(e.value,10)||0,o=this.db.prepare("SELECT rowid, * FROM oplog WHERE device_id = ? AND rowid > ? ORDER BY rowid ASC").all(this.deviceId,n);if(o.length===0)return 0;await this.remote.push(o);let r=o[o.length-1].rowid;return this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
128
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(`push_hwm_${this.deviceId}`,String(r)),o.length}async pull(){let e=0,n=0;for(;;){if(++n>this.maxPullIterations)throw new Error(`Sync pull loop exceeded ${this.maxPullIterations} iterations - aborting to prevent infinite loop. This likely indicates a bug in the remote backend.`);let r=this.db.prepare("SELECT value FROM meta WHERE key = ?").get(`pull_cursor_${this.deviceId}`)?.value??null,s=await this.remote.pull(r);if(s.entries.length===0)break;gn(this.db,s.entries),s.cursor&&this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
129
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(`pull_cursor_${this.deviceId}`,s.cursor);let a=s.entries.filter(u=>u.device_id!==this.deviceId);if(e+=a.length,s.cursor===r)break}return e}async sync(){let e=await this.push(),n=await this.pull();return{pushed:e,pulled:n}}};async function _n(t,e,n){let o=t.name,r=wr.join(Er.tmpdir(),`oru-sync-backup-${Date.now()}.db`);t.exec(`VACUUM INTO '${r.replace(/'/g,"''")}'`);try{return await new Je(t,e,n).sync()}catch(s){t.close();for(let a of["-wal","-shm"])try{_t.unlinkSync(o+a)}catch{}throw _t.copyFileSync(r,o),s}finally{e.close?.();try{_t.unlinkSync(r)}catch{}}}function yn(t){let e=t.prepare("SELECT value FROM meta WHERE key = 'device_id'").get();if(e)return e.value;let n=re();return t.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('device_id', ?)").run(n),t.prepare("SELECT value FROM meta WHERE key = 'device_id'").get().value}kt();var Ir={sunday:0,sun:0,monday:1,mon:1,tuesday:2,tue:2,wednesday:3,wed:3,thursday:4,thu:4,friday:5,fri:5,saturday:6,sat:6},hn={january:1,jan:1,february:2,feb:2,march:3,mar:3,april:4,apr:4,may:5,june:6,jun:6,july:7,jul:7,august:8,aug:8,september:9,sep:9,sept:9,october:10,oct:10,november:11,nov:11,december:12,dec:12};function St(t,e="mdy",n="monday",o="same_day",r){let s=r??new Date,a=t.trim();if(!a)return null;let{datePart:u,timePart:p}=Ar(a),m=Mr(u,e,n,o,s);if(!m)return null;if(p){let d=Pr(p);if(!d)return null;m.setHours(d.hours,d.minutes,0,0)}return Cr(m)}function Ar(t){let e=t.match(/^(\d{4}-\d{2}-\d{2})T(\d{1,2}:\d{2})$/i);if(e)return{datePart:e[1],timePart:e[2]};let n=t.split(/\s+/);if(n.length===1)return{datePart:n[0],timePart:null};let o=n[n.length-1];return Lr(o)?{datePart:n.slice(0,-1).join(" "),timePart:o}:{datePart:t,timePart:null}}function Lr(t){return/^\d{1,2}:\d{2}\s*(am?|pm?)?$/i.test(t)||/^\d{1,2}\s*(am?|pm?)$/i.test(t)}function Mr(t,e,n,o,r){let s=t.toLowerCase().trim();if(s==="today"||s==="tod")return ee(r);if(s==="tomorrow"||s==="tom"){let m=ee(r);return m.setDate(m.getDate()+1),m}if(s==="tonight"){let m=ee(r);return m.setHours(18,0,0,0),m}if(s==="next week")return Tn(r,ge[n]);if(s==="next month"){if(o==="first")return new Date(r.getFullYear(),r.getMonth()+1,1);let m=(r.getMonth()+1)%12,d=new Date(r.getFullYear(),r.getMonth()+1,r.getDate());return d.getMonth()!==m?new Date(r.getFullYear(),r.getMonth()+2,0):d}if(s==="end of month")return new Date(r.getFullYear(),r.getMonth()+1,0);if(s==="end of week"){let m=(ge[n]+6)%7,d=ee(r),_=d.getDay(),b=(m-_+7)%7;return b===0||d.setDate(d.getDate()+b),d}{let m=s;m.startsWith("next ")&&(m=m.slice(5));let d=Ir[m];if(d!==void 0)return Tn(r,d)}{let m=s.match(/^in\s+(\d+)\s+(days?|weeks?|months?)$/);if(m){let d=Number(m[1]),_=m[2],b=ee(r);if(_.startsWith("day"))b.setDate(b.getDate()+d);else if(_.startsWith("week"))b.setDate(b.getDate()+d*7);else if(_.startsWith("month")){let N=b.getDate();b.setDate(1),b.setMonth(b.getMonth()+d);let E=new Date(b.getFullYear(),b.getMonth()+1,0).getDate();b.setDate(Math.min(N,E))}return b}}{let m=s.match(/^([a-z]+)\s+(\d+)(?:st|nd|rd|th)?$/);if(m){let d=hn[m[1]];if(d!==void 0){let _=Number(m[2]);return Sn(r,d,_)}}}{let m=s.match(/^(\d+)(?:st|nd|rd|th)?\s+([a-z]+)$/);if(m){let d=hn[m[2]];if(d!==void 0){let _=Number(m[1]);return Sn(r,d,_)}}}let a=t.match(/^(\d{4})-(\d{2})-(\d{2})$/);if(a)return te(Number(a[1]),Number(a[2]),Number(a[3]));let u=t.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);if(u){let m=Number(u[1]),d=Number(u[2]),_=Number(u[3]);return kn(m,d,_,e)}let p=t.match(/^(\d{1,2})\/(\d{1,2})$/);if(p){let m=Number(p[1]),d=Number(p[2]);return kn(m,d,r.getFullYear(),e)}return null}function kn(t,e,n,o){if(o==="dmy"){let s=te(n,e,t);return s||te(n,t,e)}let r=te(n,t,e);return r||te(n,e,t)}function te(t,e,n){if(e<1||e>12||n<1||n>31)return null;let o=new Date(t,e-1,n);return o.getFullYear()!==t||o.getMonth()!==e-1||o.getDate()!==n?null:o}function Sn(t,e,n){let o=t.getFullYear(),r=te(o,e,n);return r?r<ee(t)?te(o+1,e,n):r:null}function Pr(t){let e=t.trim().toLowerCase(),n=e.match(/^(\d{1,2}):(\d{2})\s*(am?|pm?)?$/);if(n){let r=Number(n[1]),s=Number(n[2]),a=n[3];return a&&(r=vn(r,a)),r<0||r>23||s<0||s>59?null:{hours:r,minutes:s}}let o=e.match(/^(\d{1,2})\s*(am?|pm?)$/);if(o){let r=Number(o[1]);return r=vn(r,o[2]),r<0||r>23?null:{hours:r,minutes:0}}return null}function vn(t,e){let n=e.startsWith("p");return n&&t<12?t+12:!n&&t===12?0:t}function Cr(t){let e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),o=String(t.getDate()).padStart(2,"0"),r=String(t.getHours()).padStart(2,"0"),s=String(t.getMinutes()).padStart(2,"0"),a=String(t.getSeconds()).padStart(2,"0");return`${e}-${n}-${o}T${r}:${s}:${a}`}function ee(t){return new Date(t.getFullYear(),t.getMonth(),t.getDate())}function Tn(t,e){let n=ee(t),o=n.getDay(),r=e-o;return r<=0&&(r+=7),n.setDate(n.getDate()+r),n}import Tt from"fs";import Ur from"path";import Yr from"os";import{spawn as Br}from"child_process";import{stringify as Wr,parse as Jr}from"smol-toml";var Fr=new Set(["DAILY","WEEKLY","MONTHLY","YEARLY"]),jr=new Set(["MO","TU","WE","TH","FR","SA","SU"]);function he(t){if(!t||t.trim().length===0)return!1;let e=t;if(e.startsWith("after:")&&(e=e.slice(6)),!e.startsWith("FREQ="))return!1;let n=e.split(";"),o=!1;for(let r of n){let[s,a]=r.split("=");if(!s||a===void 0)return!1;switch(s){case"FREQ":if(!Fr.has(a))return!1;o=!0;break;case"INTERVAL":{let u=Number(a);if(!Number.isInteger(u)||u<1)return!1;break}case"BYDAY":for(let u of a.split(","))if(!jr.has(u))return!1;break;case"BYMONTHDAY":{let u=Number(a);if(!Number.isInteger(u)||u<1||u>31)return!1;break}default:return!1}}return o}import{z as L}from"zod";var vt=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/,ce=1e3,ue=1e4,He=200,ke=100,Se=100,wn=100,ve=50,Te=100,we=5e3;function Ve(t){return t.replace(/[\r\n]+/g," ").trim()}function En(t,{required:e=!1}={}){return t.length===0?{valid:!1,message:e?"Title is required.":"Title cannot be empty."}:t.length>ce?{valid:!1,message:`Title exceeds maximum length of ${ce} characters.`}:{valid:!0}}function xn(t){return t.length>ue?{valid:!1,message:`Note exceeds maximum length of ${ue} characters.`}:{valid:!0}}function On(t){for(let e of t){if(e.length===0)return{valid:!1,message:"Label cannot be empty."};if(e.length>He)return{valid:!1,message:`Label exceeds maximum length of ${He} characters.`}}return{valid:!0}}var Ws=L.enum($),Js=L.enum(D),Hs=L.string().min(1,"Title is required.").max(ce,`Title exceeds maximum length of ${ce} characters.`),Vs=L.string().max(ce,`Title exceeds maximum length of ${ce} characters.`),qs=L.array(L.string().max(He,`Label exceeds maximum length of ${He} characters.`)).max(ke,`labels exceeds maximum of ${ke} items.`),Xs=L.array(L.string().max(ue,`Note exceeds maximum length of ${ue} characters.`)).max(wn,`notes exceeds maximum of ${wn} items.`),Ks=L.string().max(ue,`Note exceeds maximum length of ${ue} characters.`),Gs=L.array(L.string()).max(Se,`blocked_by exceeds maximum of ${Se} items.`),zs=L.record(L.string(),L.unknown()).refine(t=>Object.keys(t).length<=ve,`Metadata exceeds maximum of ${ve} keys.`).refine(t=>Object.keys(t).every(e=>e.length<=Te),`Metadata key exceeds maximum length of ${Te} characters.`).refine(t=>Object.values(t).every(e=>typeof e!="string"||e.length<=we),`Metadata value exceeds maximum length of ${we} characters.`),Qs=L.string().regex(vt,"Invalid date format. Expected YYYY-MM-DD, YYYY-MM-DDTHH:MM, or YYYY-MM-DDTHH:MM:SS.").nullable().optional();function $n(t){let e={title:t.title,status:t.status,priority:t.priority};t.owner&&(e.owner=t.owner),t.due_at&&(e.due=t.due_at),t.recurrence&&(e.recurrence=t.recurrence),e.blocked_by=t.blocked_by,e.labels=t.labels,Object.keys(t.metadata).length>0&&(e.metadata=t.metadata);let n=`+++
130
+ `;if(n+=Wr(e),n+=`
131
131
  +++
132
132
  `,n+=`
133
133
  # Notes
134
134
  `,n+=`# Add new notes below. Delete lines to remove notes.
135
135
  `,t.notes.length>0){n+=`
136
136
  `;for(let o of t.notes)n+=`- ${o.replace(/\r?\n/g,"\\n")}
137
- `}return n}function yn(t,e){let n=t.match(/^\+\+\+\n([\s\S]*?)\n\+\+\+/);if(!n)throw new Error("Invalid document format: missing +++ delimiters.");let o=n[1],r=Pr(o),s={};if(typeof r.title=="string"&&r.title!==e.title&&(s.title=r.title),typeof r.status=="string"&&r.status!==e.status){if(!re.has(r.status))throw new Error(`Invalid status: ${r.status}.`);s.status=r.status}if(typeof r.priority=="string"&&r.priority!==e.priority){if(!oe.has(r.priority))throw new Error(`Invalid priority: ${r.priority}.`);s.priority=r.priority}let a=r.owner;a===void 0||a===""?e.owner!==null&&(s.owner=null):typeof a=="string"&&a!==e.owner&&(s.owner=a);let u=r.due;if(u===void 0||u==="")e.due_at!==null&&(s.due_at=null);else if(u instanceof Date){let S=u.toISOString().slice(0,10);S!==e.due_at&&(s.due_at=S)}else if(typeof u=="string"&&u!==e.due_at){if(!mn.test(u))throw new Error(`Invalid due date: ${u}. Expected format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS.`);if(isNaN(new Date(u).getTime()))throw new Error(`Invalid due date: ${u}. The date is not a valid calendar date.`);s.due_at=u}let p=r.recurrence;if(p===void 0||p==="")e.recurrence!==null&&e.recurrence!==void 0&&(s.recurrence=null);else if(typeof p=="string"&&p!==e.recurrence){if(!_e(p))throw new Error(`Invalid recurrence: ${p}.`);s.recurrence=p}if(Array.isArray(r.blocked_by)){let S=r.blocked_by.filter(x=>typeof x=="string");(S.length!==e.blocked_by.length||S.some((x,P)=>x!==e.blocked_by[P]))&&(s.blocked_by=S)}if(Array.isArray(r.labels)){let S=r.labels.filter(x=>typeof x=="string");(S.length!==e.labels.length||S.some((x,P)=>x!==e.labels[P]))&&(s.labels=S)}if(r.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)){let S=r.metadata,O=e.metadata;JSON.stringify(S)!==JSON.stringify(O)&&(s.metadata=S)}else!r.metadata&&Object.keys(e.metadata).length>0&&(s.metadata={});let d=t.slice(n[0].length).split(`
137
+ `}return n}function Dn(t,e){let n=t.match(/^\+\+\+\n([\s\S]*?)\n\+\+\+/);if(!n)throw new Error("Invalid document format: missing +++ delimiters.");let o=n[1],r=Jr(o),s={};if(typeof r.title=="string"&&r.title!==e.title&&(s.title=r.title),typeof r.status=="string"&&r.status!==e.status){if(!oe.has(r.status))throw new Error(`Invalid status: ${r.status}.`);s.status=r.status}if(typeof r.priority=="string"&&r.priority!==e.priority){if(!se.has(r.priority))throw new Error(`Invalid priority: ${r.priority}.`);s.priority=r.priority}let a=r.owner;a===void 0||a===""?e.owner!==null&&(s.owner=null):typeof a=="string"&&a!==e.owner&&(s.owner=a);let u=r.due;if(u===void 0||u==="")e.due_at!==null&&(s.due_at=null);else if(u instanceof Date){let S=u.toISOString().slice(0,10);S!==e.due_at&&(s.due_at=S)}else if(typeof u=="string"&&u!==e.due_at){if(!vt.test(u))throw new Error(`Invalid due date: ${u}. Expected format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS.`);if(isNaN(new Date(u).getTime()))throw new Error(`Invalid due date: ${u}. The date is not a valid calendar date.`);s.due_at=u}let p=r.recurrence;if(p===void 0||p==="")e.recurrence!==null&&e.recurrence!==void 0&&(s.recurrence=null);else if(typeof p=="string"&&p!==e.recurrence){if(!he(p))throw new Error(`Invalid recurrence: ${p}.`);s.recurrence=p}if(Array.isArray(r.blocked_by)){let S=r.blocked_by.filter(O=>typeof O=="string");(S.length!==e.blocked_by.length||S.some((O,F)=>O!==e.blocked_by[F]))&&(s.blocked_by=S)}if(Array.isArray(r.labels)){let S=r.labels.filter(O=>typeof O=="string");(S.length!==e.labels.length||S.some((O,F)=>O!==e.labels[F]))&&(s.labels=S)}if(r.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)){let S=r.metadata,x=e.metadata;JSON.stringify(S)!==JSON.stringify(x)&&(s.metadata=S)}else!r.metadata&&Object.keys(e.metadata).length>0&&(s.metadata={});let d=t.slice(n[0].length).split(`
138
138
  `).filter(S=>S.startsWith("- ")).map(S=>S.slice(2).replace(/\\n/g,`
139
- `)),_=new Set(e.notes),b=d.filter(S=>!_.has(S)),$=new Set(d),E=e.notes.some(S=>!$.has(S));return{fields:s,newNotes:b,removedNotes:E}}async function bn(t){let e=Ir.join(Lr.tmpdir(),`oru-edit-${Date.now()}.toml`);mt.writeFileSync(e,t);let o=(process.env.EDITOR||"vi").split(/\s+/),r=o.shift();return o.push(e),await new Promise((a,u)=>{let p=Ar(r,o,{stdio:"inherit"});p.on("exit",m=>{m===0?a():u(new Error(`Editor exited with code ${m}. No changes were saved.`))}),p.on("error",u)}),{edited:mt.readFileSync(e,"utf-8"),tmpFile:e}}function hn(t){try{mt.unlinkSync(t)}catch{}}var Cr={monday:"MO",tuesday:"TU",wednesday:"WE",thursday:"TH",friday:"FR",saturday:"SA",sunday:"SU",mon:"MO",tue:"TU",wed:"WE",thu:"TH",fri:"FR",sat:"SA",sun:"SU"};function Fe(t){let e=t.trim();if(e.length===0)throw new Error("Empty recurrence value.");let n="",o=e;if(e.toLowerCase().startsWith("after:")&&(n="after:",o=e.slice(6).trim()),o.startsWith("FREQ=")){let p=`${n}${o}`;if(!_e(p))throw new Error(`Invalid RRULE: ${o}`);return p}let r=o.toLowerCase();switch(r){case"daily":return`${n}FREQ=DAILY`;case"weekly":return`${n}FREQ=WEEKLY`;case"monthly":return`${n}FREQ=MONTHLY`;case"yearly":return`${n}FREQ=YEARLY`;case"weekdays":return`${n}FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR`}let s=r.match(/^every\s+(\d+)\s+(day|week|month|year)s?$/);if(s){let p=Number(s[1]),m=s[2].toUpperCase(),_={DAY:"DAILY",WEEK:"WEEKLY",MONTH:"MONTHLY",YEAR:"YEARLY"}[m]??"YEARLY";return`${n}FREQ=${_};INTERVAL=${p}`}let a=r.match(/^every\s+([a-z,]+)$/);if(a){let p=a[1].split(",").map(d=>d.trim()),m=[];for(let d of p){let _=Cr[d];if(!_)throw new Error(`Unknown day: ${d}. Use: monday, tuesday, ..., or mon, tue, ...`);m.push(_)}return`${n}FREQ=WEEKLY;BYDAY=${m.join(",")}`}let u=r.match(/^every\s+(\d+)(?:st|nd|rd|th)$/);if(u){let p=Number(u[1]);if(p<1||p>31)throw new Error(`Invalid month day: ${p}. Must be 1-31.`);return`${n}FREQ=MONTHLY;BYMONTHDAY=${p}`}throw new Error(`Could not parse recurrence: "${e}". Try: daily, weekly, monthly, every 3 days, every monday, every mon,wed,fri, every 15th, or raw RRULE like FREQ=DAILY.`)}le();async function pt(t,e,n){if(e==="tasks")return(await t.list()).filter(r=>r.id.startsWith(n)).slice(0,50).map(r=>`${r.id} ${r.title}`);if(e==="labels"){let o=await t.list(),r=new Set;for(let s of o)for(let a of s.labels)a.startsWith(n)&&r.add(a);return[...r].sort().slice(0,50)}return[]}le();function ye(){let t=["add","list","labels","get","update","edit","delete","done","start","review","context","log","sync","config","filter",...[],"backup","completions","self-update","telemetry"].join(" "),e=["add","list","labels","get","update","edit","delete","done","start","review","context","log","sync","config","filter",...[],"backup","completions","self-update","telemetry"].join("|"),n="";return`# oru shell completions for bash
139
+ `)),_=new Set(e.notes),b=d.filter(S=>!_.has(S)),N=new Set(d),E=e.notes.some(S=>!N.has(S));return{fields:s,newNotes:b,removedNotes:E}}async function Nn(t){let e=Ur.join(Yr.tmpdir(),`oru-edit-${Date.now()}.toml`);Tt.writeFileSync(e,t);let o=(process.env.EDITOR||"vi").split(/\s+/),r=o.shift();return o.push(e),await new Promise((a,u)=>{let p=Br(r,o,{stdio:"inherit"});p.on("exit",m=>{m===0?a():u(new Error(`Editor exited with code ${m}. No changes were saved.`))}),p.on("error",u)}),{edited:Tt.readFileSync(e,"utf-8"),tmpFile:e}}function Rn(t){try{Tt.unlinkSync(t)}catch{}}var Hr={monday:"MO",tuesday:"TU",wednesday:"WE",thursday:"TH",friday:"FR",saturday:"SA",sunday:"SU",mon:"MO",tue:"TU",wed:"WE",thu:"TH",fri:"FR",sat:"SA",sun:"SU"};function qe(t){let e=t.trim();if(e.length===0)throw new Error("Empty recurrence value.");let n="",o=e;if(e.toLowerCase().startsWith("after:")&&(n="after:",o=e.slice(6).trim()),o.startsWith("FREQ=")){let p=`${n}${o}`;if(!he(p))throw new Error(`Invalid RRULE: ${o}`);return p}let r=o.toLowerCase();switch(r){case"daily":return`${n}FREQ=DAILY`;case"weekly":return`${n}FREQ=WEEKLY`;case"monthly":return`${n}FREQ=MONTHLY`;case"yearly":return`${n}FREQ=YEARLY`;case"weekdays":return`${n}FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR`}let s=r.match(/^every\s+(\d+)\s+(day|week|month|year)s?$/);if(s){let p=Number(s[1]),m=s[2].toUpperCase(),_={DAY:"DAILY",WEEK:"WEEKLY",MONTH:"MONTHLY",YEAR:"YEARLY"}[m]??"YEARLY";return`${n}FREQ=${_};INTERVAL=${p}`}let a=r.match(/^every\s+([a-z,]+)$/);if(a){let p=a[1].split(",").map(d=>d.trim()),m=[];for(let d of p){let _=Hr[d];if(!_)throw new Error(`Unknown day: ${d}. Use: monday, tuesday, ..., or mon, tue, ...`);m.push(_)}return`${n}FREQ=WEEKLY;BYDAY=${m.join(",")}`}let u=r.match(/^every\s+(\d+)(?:st|nd|rd|th)$/);if(u){let p=Number(u[1]);if(p<1||p>31)throw new Error(`Invalid month day: ${p}. Must be 1-31.`);return`${n}FREQ=MONTHLY;BYMONTHDAY=${p}`}throw new Error(`Could not parse recurrence: "${e}". Try: daily, weekly, monthly, every 3 days, every monday, every mon,wed,fri, every 15th, or raw RRULE like FREQ=DAILY.`)}de();async function wt(t,e,n){if(e==="tasks")return(await t.list()).filter(r=>r.id.startsWith(n)).slice(0,50).map(r=>`${r.id} ${r.title}`);if(e==="labels"){let o=await t.list(),r=new Set;for(let s of o)for(let a of s.labels)a.startsWith(n)&&r.add(a);return[...r].sort().slice(0,50)}return[]}de();function Ee(){let t=["add","list","labels","get","update","edit","delete","done","start","review","context","log","sync","config","filter",...[],"backup","completions","self-update","telemetry"].join(" "),e=["add","list","labels","get","update","edit","delete","done","start","review","context","log","sync","config","filter",...[],"backup","completions","self-update","telemetry"].join("|"),n="";return`# oru shell completions for bash
140
140
  # Install: oru completions bash
141
141
  # Print: oru completions bash --print
142
142
 
@@ -149,8 +149,8 @@ _oru_completions() {
149
149
  local filter_subcommands="list show add remove"${""}
150
150
  local telemetry_subcommands="status enable disable"
151
151
  local completion_shells="bash zsh fish"
152
- local status_values="${D.join(" ")}"
153
- local priority_values="${N.join(" ")}"
152
+ local status_values="${$.join(" ")}"
153
+ local priority_values="${D.join(" ")}"
154
154
  local sort_values="${B.join(" ")}"
155
155
 
156
156
  # Determine the subcommand
@@ -266,7 +266,7 @@ ${n}
266
266
  }
267
267
 
268
268
  complete -F _oru_completions oru
269
- `}le();function be(){return`#compdef oru
269
+ `}de();function xe(){return`#compdef oru
270
270
  # oru shell completions for zsh
271
271
  # Install: oru completions zsh
272
272
  # Print: oru completions zsh --print
@@ -296,10 +296,10 @@ _oru() {
296
296
  )
297
297
 
298
298
  local -a status_values
299
- status_values=(${D.join(" ")})
299
+ status_values=(${$.join(" ")})
300
300
 
301
301
  local -a priority_values
302
- priority_values=(${N.join(" ")})
302
+ priority_values=(${D.join(" ")})
303
303
 
304
304
  local -a sort_values
305
305
  sort_values=(${B.join(" ")})
@@ -494,7 +494,7 @@ ${""} completions)
494
494
  }
495
495
 
496
496
  _oru "$@"
497
- `}le();function he(){return`# oru shell completions for fish
497
+ `}de();function Oe(){return`# oru shell completions for fish
498
498
  # Install: oru completions fish
499
499
  # Print: oru completions fish --print
500
500
 
@@ -558,8 +558,8 @@ complete -c oru -n '__oru_using_command filter' -a list -d 'List all saved filte
558
558
  complete -c oru -n '__oru_using_command filter' -a show -d 'Show a filter definition'
559
559
  complete -c oru -n '__oru_using_command filter' -a add -d 'Save a new named filter'
560
560
  complete -c oru -n '__oru_using_command filter' -a remove -d 'Delete a saved filter'
561
- complete -c oru -n '__oru_using_subcommand filter add' -s s -l status -a '${D.join(" ")}' -d 'Status' -r
562
- complete -c oru -n '__oru_using_subcommand filter add' -s p -l priority -a '${N.join(" ")}' -d 'Priority' -r
561
+ complete -c oru -n '__oru_using_subcommand filter add' -s s -l status -a '${$.join(" ")}' -d 'Status' -r
562
+ complete -c oru -n '__oru_using_subcommand filter add' -s p -l priority -a '${D.join(" ")}' -d 'Priority' -r
563
563
  complete -c oru -n '__oru_using_subcommand filter add' -s l -l label -a '(__oru_labels)' -d 'Label' -r
564
564
  complete -c oru -n '__oru_using_subcommand filter add' -l owner -d 'Filter by owner' -r
565
565
  complete -c oru -n '__oru_using_subcommand filter add' -l due -d 'Filter by due date' -r
@@ -598,14 +598,14 @@ complete -c oru -n '__oru_using_command backup' -a '(__fish_complete_directories
598
598
  complete -c oru -n '__oru_using_command sync' -F
599
599
 
600
600
  # Status flag for add, list, update, edit
601
- complete -c oru -n '__oru_using_command add' -s s -l status -a '${D.join(" ")}' -d 'Status' -r
602
- complete -c oru -n '__oru_using_command list' -s s -l status -a '${D.join(" ")}' -d 'Status' -r
603
- complete -c oru -n '__oru_using_command update' -s s -l status -a '${D.join(" ")}' -d 'Status' -r
601
+ complete -c oru -n '__oru_using_command add' -s s -l status -a '${$.join(" ")}' -d 'Status' -r
602
+ complete -c oru -n '__oru_using_command list' -s s -l status -a '${$.join(" ")}' -d 'Status' -r
603
+ complete -c oru -n '__oru_using_command update' -s s -l status -a '${$.join(" ")}' -d 'Status' -r
604
604
 
605
605
  # Priority flag for add, list, update, edit
606
- complete -c oru -n '__oru_using_command add' -s p -l priority -a '${N.join(" ")}' -d 'Priority' -r
607
- complete -c oru -n '__oru_using_command list' -s p -l priority -a '${N.join(" ")}' -d 'Priority' -r
608
- complete -c oru -n '__oru_using_command update' -s p -l priority -a '${N.join(" ")}' -d 'Priority' -r
606
+ complete -c oru -n '__oru_using_command add' -s p -l priority -a '${D.join(" ")}' -d 'Priority' -r
607
+ complete -c oru -n '__oru_using_command list' -s p -l priority -a '${D.join(" ")}' -d 'Priority' -r
608
+ complete -c oru -n '__oru_using_command update' -s p -l priority -a '${D.join(" ")}' -d 'Priority' -r
609
609
 
610
610
  # Label flag
611
611
  complete -c oru -n '__oru_using_command add' -s l -l label -a '(__oru_labels)' -d 'Label' -r
@@ -671,53 +671,53 @@ complete -c oru -n '__oru_using_command sync' -l plaintext -d 'Output as plain t
671
671
 
672
672
  # self-update flags
673
673
  complete -c oru -n '__oru_using_command self-update' -l check -d 'Only check if an update is available'
674
- `}import je from"fs";import K from"path";import Fr from"os";import jr from"readline";function ft(){let t=process.env.SHELL;if(!t)return null;let e=K.basename(t);return e==="bash"||e==="zsh"||e==="fish"?e:null}function kn(t,e=Fr.homedir()){switch(t){case"bash":return{scriptPath:K.join(e,".oru","completions.bash"),rcPath:K.join(e,".bashrc")};case"zsh":return{scriptPath:K.join(e,".oru","completions.zsh"),rcPath:K.join(e,".zshrc")};case"fish":return{scriptPath:K.join(e,".config","fish","completions","oru.fish"),rcPath:null}}}function Ur(t){switch(t){case"bash":return ye();case"zsh":return be();case"fish":return he()}}function Ue(t,e,n){let{scriptPath:o,rcPath:r}=kn(t,n);je.mkdirSync(K.dirname(o),{recursive:!0}),je.writeFileSync(o,Ur(t)),e(`Wrote completions to ${o}`);let s=!1;if(r){let a=`source ${o}`,u=`source ~/.oru/completions.${t}`,p="";try{p=je.readFileSync(r,"utf-8")}catch{}if(!p.includes(a)&&!p.includes(u)){let m=p.length>0&&!p.endsWith(`
674
+ `}import Xe from"fs";import G from"path";import Vr from"os";import qr from"readline";function Et(){let t=process.env.SHELL;if(!t)return null;let e=G.basename(t);return e==="bash"||e==="zsh"||e==="fish"?e:null}function In(t,e=Vr.homedir()){switch(t){case"bash":return{scriptPath:G.join(e,".oru","completions.bash"),rcPath:G.join(e,".bashrc")};case"zsh":return{scriptPath:G.join(e,".oru","completions.zsh"),rcPath:G.join(e,".zshrc")};case"fish":return{scriptPath:G.join(e,".config","fish","completions","oru.fish"),rcPath:null}}}function Xr(t){switch(t){case"bash":return Ee();case"zsh":return xe();case"fish":return Oe()}}function Ke(t,e,n){let{scriptPath:o,rcPath:r}=In(t,n);Xe.mkdirSync(G.dirname(o),{recursive:!0}),Xe.writeFileSync(o,Xr(t)),e(`Wrote completions to ${o}`);let s=!1;if(r){let a=`source ${o}`,u=`source ~/.oru/completions.${t}`,p="";try{p=Xe.readFileSync(r,"utf-8")}catch{}if(!p.includes(a)&&!p.includes(u)){let m=p.length>0&&!p.endsWith(`
675
675
  `)?`
676
- `:"";je.appendFileSync(r,`${m}${a}
677
- `),s=!0,e(`Added source line to ${r}`)}else e(`Source line already present in ${r}`)}return{shell:t,scriptPath:o,rcPath:r,sourceLineAdded:s}}function gt(t,e=process.stdin,n=process.stdout){return new Promise(o=>{let r=jr.createInterface({input:e,output:n});r.question(t,s=>{r.close();let a=s.trim().toLowerCase();o(a===""||a==="y"||a==="yes")})})}function Be(t){return t.rcPath?`
678
- Restart your shell or run: source ~/${K.basename(t.rcPath)}`:`
679
- Completions will be loaded automatically on next shell start.`}import Ye from"fs";import _t from"path";import Br from"os";import{parse as Yr}from"smol-toml";var yt=["status","priority","owner","label","search","sort","actionable","due","overdue","all","limit","offset"];function Sn(){return process.env.ORU_CONFIG_DIR?_t.join(process.env.ORU_CONFIG_DIR,"filters.toml"):_t.join(Br.homedir(),".oru","filters.toml")}function ce(t){let e=t??Sn();if(!Ye.existsSync(e))return{};let n=Ye.readFileSync(e,"utf-8");try{return Yr(n)}catch(o){return process.stderr.write(`Warning: Could not parse filters file at ${e}: ${o instanceof Error?o.message:String(o)}. Ignoring.
680
- `),{}}}function Wr(t){return Array.isArray(t)?`[${t.map(e=>`"${String(e).replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ")}]`:typeof t=="string"?`"${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`:typeof t=="boolean"||typeof t=="number"?String(t):`"${String(t)}"`}function Jr(t){return`["${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"]`}function Hr(t){let e=[];for(let[n,o]of Object.entries(t)){let r=[Jr(n)];for(let[s,a]of Object.entries(o))a!==void 0&&r.push(`${s} = ${Wr(a)}`);e.push(r.join(`
676
+ `:"";Xe.appendFileSync(r,`${m}${a}
677
+ `),s=!0,e(`Added source line to ${r}`)}else e(`Source line already present in ${r}`)}return{shell:t,scriptPath:o,rcPath:r,sourceLineAdded:s}}function xt(t,e=process.stdin,n=process.stdout){return new Promise(o=>{let r=qr.createInterface({input:e,output:n});r.question(t,s=>{r.close();let a=s.trim().toLowerCase();o(a===""||a==="y"||a==="yes")})})}function Ge(t){return t.rcPath?`
678
+ Restart your shell or run: source ~/${G.basename(t.rcPath)}`:`
679
+ Completions will be loaded automatically on next shell start.`}import ze from"fs";import Ot from"path";import Kr from"os";import{parse as Gr}from"smol-toml";var $t=["status","priority","owner","label","search","sort","actionable","due","overdue","all","limit","offset"];function An(){return process.env.ORU_CONFIG_DIR?Ot.join(process.env.ORU_CONFIG_DIR,"filters.toml"):Ot.join(Kr.homedir(),".oru","filters.toml")}function me(t){let e=t??An();if(!ze.existsSync(e))return{};let n=ze.readFileSync(e,"utf-8");try{return Gr(n)}catch(o){return process.stderr.write(`Warning: Could not parse filters file at ${e}: ${o instanceof Error?o.message:String(o)}. Ignoring.
680
+ `),{}}}function zr(t){return Array.isArray(t)?`[${t.map(e=>`"${String(e).replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ")}]`:typeof t=="string"?`"${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`:typeof t=="boolean"||typeof t=="number"?String(t):`"${String(t)}"`}function Qr(t){return`["${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"]`}function Zr(t){let e=[];for(let[n,o]of Object.entries(t)){let r=[Qr(n)];for(let[s,a]of Object.entries(o))a!==void 0&&r.push(`${s} = ${zr(a)}`);e.push(r.join(`
681
681
  `))}return e.join(`
682
682
 
683
683
  `)+(e.length>0?`
684
- `:"")}function bt(t,e){let n=e??Sn();Ye.mkdirSync(_t.dirname(n),{recursive:!0}),Ye.writeFileSync(n,Hr(t))}function vn(t,e){let n={...t};for(let o of yt)t[o]===void 0&&e[o]!==void 0&&(n[o]=e[o]);return n}We();vt();ke();function jn(t){let e={};for(let n of t){let o=n.indexOf("=");if(o===-1){n.trim()&&(e[n.trim()]=null);continue}let r=n.slice(0,o).trim(),s=n.slice(o+1);r&&(e[r]=s)}return e}function Un(t){return t.replace(/\b(update -s \w+)\b/g,(e,n)=>de(n))}function yo(t){let e="";return t.split(`
685
- `).map(n=>{if(/^(Options|Commands|Arguments):$/.test(n))return e=n.slice(0,-1).toLowerCase(),L(n);if(n.startsWith("Usage: "))return e="",`${L("Usage:")} ${n.slice(7)}`;if(n.trim()!==""&&!n.startsWith(" "))return e="",n;let o=n.match(/^(\s{2})(\S.*?)(\s{2,})(.*)/);if(o){let[,r,s,a,u]=o;return s.startsWith("-")?r+L(s)+a+u:r+de(s)+a+w(Un(u))}return e==="commands"&&n.match(/^\s{4,}\S/)?w(Un(n)):n}).join(`
686
- `)}var bo={formatHelp(t,e){let n=_o.prototype.formatHelp.call(e,t,e);return yo(n)}};function Bn(t){t.configureHelp(bo);for(let e of t.commands)Bn(e)}function ho(t,e=r=>process.stdout.write(`${r}
687
- `),n,o=r=>process.stderr.write(r)){let r=n??ct(),s=Ht(t),a=on(t),u=new Ae(s,a),p=new go("oru").description(`${L("oru")} - personal task manager that your agents can operate for you
684
+ `:"")}function Dt(t,e){let n=e??An();ze.mkdirSync(Ot.dirname(n),{recursive:!0}),ze.writeFileSync(n,Zr(t))}function Ln(t,e){let n={...t};for(let o of $t)t[o]===void 0&&e[o]!==void 0&&(n[o]=e[o]);return n}Qe();At();$e();function Vn(t){let e={};for(let n of t){let o=n.indexOf("=");if(o===-1){n.trim()&&(e[n.trim()]=null);continue}let r=n.slice(0,o).trim(),s=n.slice(o+1);r&&(e[r]=s)}return e}function qn(t){return t.replace(/\b(update -s \w+)\b/g,(e,n)=>fe(n))}function Eo(t){let e="";return t.split(`
685
+ `).map(n=>{if(/^(Options|Commands|Arguments):$/.test(n))return e=n.slice(0,-1).toLowerCase(),A(n);if(n.startsWith("Usage: "))return e="",`${A("Usage:")} ${n.slice(7)}`;if(n.trim()!==""&&!n.startsWith(" "))return e="",n;let o=n.match(/^(\s{2})(\S.*?)(\s{2,})(.*)/);if(o){let[,r,s,a,u]=o;return s.startsWith("-")?r+A(s)+a+u:r+fe(s)+a+w(qn(u))}return e==="commands"&&n.match(/^\s{4,}\S/)?w(qn(n)):n}).join(`
686
+ `)}var xo={formatHelp(t,e){let n=wo.prototype.formatHelp.call(e,t,e);return Eo(n)}};function Xn(t){t.configureHelp(xo);for(let e of t.commands)Xn(e)}function Oo(t,e=r=>process.stdout.write(`${r}
687
+ `),n,o=r=>process.stderr.write(r)){let r=n??ht(),s=rn(t),a=yn(t),u=new Be(s,a),p=new To("oru").description(`${A("oru")} - personal task manager that your agents can operate for you
688
688
 
689
- Use --json on any command for machine-readable output (or set ORU_FORMAT=json, or output_format in config). Run 'oru config init' to create a config file. Set ORU_DEBUG=1 for verbose error output.`).version(`${W} (${Tn})`);p.configureOutput({writeOut:e,writeErr:e}),p.exitOverride();function m(c){return c.plaintext?!1:!!(c.json||process.env.ORU_FORMAT==="json"||r.output_format==="json")}function d(c,i){e(i?JSON.stringify({error:"ambiguous_prefix",id:c.prefix,matches:c.matches}):`Prefix '${c.prefix}' is ambiguous, matches: ${c.matches.join(", ")}.`),process.exitCode=1}function _(c,i){e(c?JSON.stringify({error:"validation",message:i}):i),process.exitCode=1}function b(c,i){e(c?JSON.stringify({error:"not_found",id:i}):`Task ${i} not found.`),process.exitCode=1}function $(c,i){let l=pn(c);return l.valid?!0:(_(i,l.message),!1)}function E(c,i){let l=fn(c);return l.valid?!0:(_(i,l.message),!1)}function S(c,i){if(c.length>100)return _(i,`labels exceeds maximum of ${100} items.`),!1;let l=gn(c);return l.valid?!0:(_(i,l.message),!1)}async function O(c,i,l,f){try{let y=await l();if(!y){b(i,c);return}f(y)}catch(y){if(y instanceof Q){d(y,i);return}throw y}}function x(c,i){return c.length>100?(_(i,`blocked_by exceeds maximum of ${100} items.`),!1):!0}function P(c,i){if(Object.keys(c).length>50)return _(i,`Metadata exceeds maximum of ${50} keys.`),!1;for(let l of Object.keys(c))if(l.length>100)return _(i,`Metadata key exceeds maximum length of ${100} characters.`),!1;for(let l of Object.values(c))if(typeof l=="string"&&l.length>5e3)return _(i,`Metadata value exceeds maximum length of ${5e3} characters.`),!1;return!0}p.command("add <title>").description("Add a new task").option("--id <id>","Task ID (for idempotent creates)").addOption(new z("-s, --status <status>","Initial status").choices(D).default("todo")).addOption(new z("-p, --priority <priority>","Priority level").choices(N).default("medium")).option("-d, --due <date>","Due date (e.g. 'tomorrow', 'Monday', 'mon 9am', 'in 3 days', 'end of week', '2026-03-20')").option("--assign <owner>","Assign to owner").option("-l, --label <labels...>","Add labels").option("-b, --blocked-by <ids...>","IDs of tasks that block this task").option("-n, --note <note>","Add an initial note").option("-r, --repeat <rule>","Recurrence rule (e.g. daily, weekly, 'every monday', FREQ=DAILY)").option("--meta <key=value...>","Metadata key=value pairs (key alone removes it)").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
689
+ Use --json on any command for machine-readable output (or set ORU_FORMAT=json, or output_format in config). Run 'oru config init' to create a config file. Set ORU_DEBUG=1 for verbose error output.`).version(`${J} (${Mn})`);p.configureOutput({writeOut:e,writeErr:e}),p.exitOverride();function m(c){return c.plaintext?!1:!!(c.json||process.env.ORU_FORMAT==="json"||r.output_format==="json")}function d(c,i){e(i?JSON.stringify({error:"ambiguous_prefix",id:c.prefix,matches:c.matches}):`Prefix '${c.prefix}' is ambiguous, matches: ${c.matches.join(", ")}.`),process.exitCode=1}function _(c,i){e(c?JSON.stringify({error:"validation",message:i}):i),process.exitCode=1}function b(c,i){e(c?JSON.stringify({error:"not_found",id:i}):`Task ${i} not found.`),process.exitCode=1}function N(c,i){let l=En(c);return l.valid?!0:(_(i,l.message),!1)}function E(c,i){let l=xn(c);return l.valid?!0:(_(i,l.message),!1)}function S(c,i){if(c.length>ke)return _(i,`labels exceeds maximum of ${ke} items.`),!1;let l=On(c);return l.valid?!0:(_(i,l.message),!1)}async function x(c,i,l,f){try{let y=await l();if(!y){b(i,c);return}f(y)}catch(y){if(y instanceof Z){d(y,i);return}throw y}}function O(c,i){return c.length>Se?(_(i,`blocked_by exceeds maximum of ${Se} items.`),!1):!0}function F(c,i){if(Object.keys(c).length>ve)return _(i,`Metadata exceeds maximum of ${ve} keys.`),!1;for(let l of Object.keys(c))if(l.length>Te)return _(i,`Metadata key exceeds maximum length of ${Te} characters.`),!1;for(let l of Object.values(c))if(typeof l=="string"&&l.length>we)return _(i,`Metadata value exceeds maximum length of ${we} characters.`),!1;return!0}p.command("add <title>").description("Add a new task").option("--id <id>","Task ID (for idempotent creates)").addOption(new Q("-s, --status <status>","Initial status").choices($).default("todo")).addOption(new Q("-p, --priority <priority>","Priority level").choices(D).default("medium")).option("-d, --due <date>","Due date (e.g. 'tomorrow', 'Monday', 'mon 9am', 'in 3 days', 'end of week', '2026-03-20')").option("--assign <owner>","Assign to owner").option("-l, --label <labels...>","Add labels").option("-b, --blocked-by <ids...>","IDs of tasks that block this task").option("-n, --note <note>","Add an initial note").option("-r, --repeat <rule>","Recurrence rule (e.g. daily, weekly, 'every monday', FREQ=DAILY)").option("--meta <key=value...>","Metadata key=value pairs (key alone removes it)").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
690
690
  Examples:
691
691
  $ oru add "Fix login bug"
692
692
  $ oru add "Fix login bug" -p high -d friday
693
693
  $ oru add "Write docs" -l docs -n "Include API section"
694
694
  $ oru add "Deploy v2" -s todo -d 2026-03-01 --assign alice
695
- $ oru add "Water plants" -r "every 3 days" -d today`).action(async(c,i)=>{c=Ce(c);let l=m(i);if(!$(c,l)||i.note&&!E(i.note,l)||i.label&&!S(i.label,l)||i.blockedBy&&!x(i.blockedBy,l))return;if(i.blockedBy){let T=await u.validateBlockedBy(null,i.blockedBy);if(!T.valid){_(l,T.error);return}}if(i.id&&!At(i.id)){_(l,`Invalid ID format: "${i.id}". IDs must be 11-character base62 strings.`);return}if(i.id){let T=await u.get(i.id);if(T){e(l?V(T):Y(T));return}}let f;if(i.due){let T=dt(i.due,r.date_format,r.first_day_of_week,r.next_month);if(!T){_(l,`Could not parse due date: ${i.due}. Try: today, tomorrow, next friday, 2026-03-01, or march 15.`);return}f=T}let y;if(i.repeat)try{y=Fe(i.repeat)}catch(T){_(l,T instanceof Error?T.message:String(T));return}let v=i.meta?jn(i.meta):void 0;if(v&&!P(v,l))return;let k=i.assign!==void 0&&i.assign.trim()===""?null:i.assign,h=await u.add({title:c,id:i.id,status:i.status,priority:i.priority,owner:k,due_at:f,recurrence:y,blocked_by:i.blockedBy,labels:i.label??void 0,notes:i.note?[i.note]:void 0,metadata:v});e(l?V(h):Y(h))}),p.command("list").description("List tasks (hides done tasks by default)").option("-s, --status <status>","Filter by status (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!D.includes(l))throw new Error(`Invalid status: ${l}. Allowed: ${D.join(", ")}`);return i.length===1?i[0]:i}).option("-p, --priority <priority>","Filter by priority (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!N.includes(l))throw new Error(`Invalid priority: ${l}. Allowed: ${N.join(", ")}`);return i.length===1?i[0]:i}).option("-l, --label <label>","Filter by label").option("--owner <owner>","Filter by owner").addOption(new z("--due <range>","Filter by due date").choices(["today","this-week"])).option("--overdue","Show only overdue tasks").addOption(new z("--sort <field>","Sort order").choices(B)).option("--search <query>","Search tasks by title").option("-a, --all","Include done tasks").option("--actionable","Show only tasks with no incomplete blockers").option("--limit <n>","Maximum number of tasks to return",Number).option("--offset <n>","Number of tasks to skip",Number).option("--filter <name>","Apply a saved filter (see 'oru filter list')").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
695
+ $ oru add "Water plants" -r "every 3 days" -d today`).action(async(c,i)=>{c=Ve(c);let l=m(i);if(!N(c,l)||i.note&&!E(i.note,l)||i.label&&!S(i.label,l)||i.blockedBy&&!O(i.blockedBy,l))return;if(i.blockedBy){let T=await u.validateBlockedBy(null,i.blockedBy);if(!T.valid){_(l,T.error);return}}if(i.id&&!Ht(i.id)){_(l,`Invalid ID format: "${i.id}". IDs must be 11-character base62 strings.`);return}if(i.id){let T=await u.get(i.id);if(T){e(l?q(T):W(T));return}}let f;if(i.due){let T=St(i.due,r.date_format,r.first_day_of_week,r.next_month);if(!T){_(l,`Could not parse due date: ${i.due}. Try: today, tomorrow, next friday, 2026-03-01, or march 15.`);return}f=T}let y;if(i.repeat)try{y=qe(i.repeat)}catch(T){_(l,T instanceof Error?T.message:String(T));return}let v=i.meta?Vn(i.meta):void 0;if(v&&!F(v,l))return;let k=i.assign!==void 0&&i.assign.trim()===""?null:i.assign,h=await u.add({title:c,id:i.id,status:i.status,priority:i.priority,owner:k,due_at:f,recurrence:y,blocked_by:i.blockedBy,labels:i.label??void 0,notes:i.note?[i.note]:void 0,metadata:v});e(l?q(h):W(h))}),p.command("list").description("List tasks (hides done tasks by default)").option("-s, --status <status>","Filter by status (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!$.includes(l))throw new Error(`Invalid status: ${l}. Allowed: ${$.join(", ")}`);return i.length===1?i[0]:i}).option("-p, --priority <priority>","Filter by priority (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!D.includes(l))throw new Error(`Invalid priority: ${l}. Allowed: ${D.join(", ")}`);return i.length===1?i[0]:i}).option("-l, --label <label>","Filter by label").option("--owner <owner>","Filter by owner").addOption(new Q("--due <range>","Filter by due date").choices(["today","this-week"])).option("--overdue","Show only overdue tasks").addOption(new Q("--sort <field>","Sort order").choices(B)).option("--search <query>","Search tasks by title").option("-a, --all","Include done tasks").option("--actionable","Show only tasks with no incomplete blockers").option("--limit <n>","Maximum number of tasks to return",Number).option("--offset <n>","Number of tasks to skip",Number).option("--filter <name>","Apply a saved filter (see 'oru filter list')").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
696
696
  Examples:
697
697
  $ oru list
698
698
  $ oru list -s in_progress -p high
699
699
  $ oru list -l backend --sort due --actionable
700
700
  $ oru list --search "login" --all
701
- $ oru list --filter mine`).action(async c=>{let i=m(c),l=c,f;if(c.filter){let k=ce()[c.filter];if(!k){_(i,`Filter '${c.filter}' not found. Run 'oru filter list' to see available filters.`);return}l={...c,...vn(c,k)},f=k.sql}let y=await u.list({status:l.status,priority:l.priority,owner:l.owner,label:l.label,search:l.search,sort:l.sort,actionable:l.actionable,limit:l.limit,offset:l.offset,sql:f});y=Ct(y,l),l.due&&(y=et(y,l.due,void 0,r.first_day_of_week)),l.overdue&&(y=et(y,"overdue")),e(i?Qt(y):Ze(y))}),p.command("labels").description("List all labels in use").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(async c=>{let i=await u.listLabels(),l=m(c);e(l?Zt(i):Bt(i))}),p.command("get <id>").description("Get a task by ID").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
701
+ $ oru list --filter mine`).action(async c=>{let i=m(c),l=c,f;if(c.filter){let k=me()[c.filter];if(!k){_(i,`Filter '${c.filter}' not found. Run 'oru filter list' to see available filters.`);return}l={...c,...Ln(c,k)},f=k.sql}let y=await u.list({status:l.status,priority:l.priority,owner:l.owner,label:l.label,search:l.search,sort:l.sort,actionable:l.actionable,limit:l.limit,offset:l.offset,sql:f});y=Kt(y,l),l.due&&(y=ut(y,l.due,void 0,r.first_day_of_week)),l.overdue&&(y=ut(y,"overdue")),e(i?dn(y):ct(y))}),p.command("labels").description("List all labels in use").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(async c=>{let i=await u.listLabels(),l=m(c);e(l?mn(i):Zt(i))}),p.command("get <id>").description("Get a task by ID").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
702
702
  Examples:
703
703
  $ oru get 019414a3
704
- $ oru get 019414a3 --json`).action(async(c,i)=>{let l=m(i);await O(c,l,()=>u.get(c),f=>{e(l?V(f):Y(f))})}),p.command("update <id>").description("Update a task").option("-t, --title <title>","New title").addOption(new z("-s, --status <status>","New status").choices(D)).addOption(new z("-p, --priority <priority>","New priority").choices(N)).option("-d, --due <date>","Due date (e.g. 'tomorrow', 'Monday', 'in 3 days', 'end of week', '2026-03-20', 'none' to clear)").option("--assign <owner>","Assign to owner ('none' to clear)").option("-l, --label <labels...>","Add labels").option("--unlabel <labels...>","Remove labels").option("-b, --blocked-by <ids...>","Set blocker task IDs (replaces full list)").option("--unblock <ids...>","Remove specific blocker task IDs").option("-n, --note <note>","Append a note").option("--clear-notes","Remove all notes").option("-r, --repeat <rule>","Recurrence rule ('none' to clear)").option("--meta <key=value...>","Metadata key=value pairs (key alone removes it)").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
704
+ $ oru get 019414a3 --json`).action(async(c,i)=>{let l=m(i);await x(c,l,()=>u.get(c),f=>{e(l?q(f):W(f))})}),p.command("update <id>").description("Update a task").option("-t, --title <title>","New title").addOption(new Q("-s, --status <status>","New status").choices($)).addOption(new Q("-p, --priority <priority>","New priority").choices(D)).option("-d, --due <date>","Due date (e.g. 'tomorrow', 'Monday', 'in 3 days', 'end of week', '2026-03-20', 'none' to clear)").option("--assign <owner>","Assign to owner ('none' to clear)").option("-l, --label <labels...>","Add labels").option("--unlabel <labels...>","Remove labels").option("-b, --blocked-by <ids...>","Set blocker task IDs (replaces full list)").option("--unblock <ids...>","Remove specific blocker task IDs").option("-n, --note <note>","Append a note").option("--clear-notes","Remove all notes").option("-r, --repeat <rule>","Recurrence rule ('none' to clear)").option("--meta <key=value...>","Metadata key=value pairs (key alone removes it)").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
705
705
  Examples:
706
706
  $ oru update 019414a3 -s in_progress
707
707
  $ oru update 019414a3 -l urgent -d tomorrow
708
708
  $ oru update 019414a3 -n "Blocked on API review"
709
709
  $ oru update 019414a3 -t "New title" -p high
710
- $ oru update 019414a3 -r "every monday"`).action(async(c,i)=>{let l=m(i);if(i.title!==void 0&&(i.title=Ce(i.title)),!(i.title!==void 0&&!$(i.title,l))&&!(i.note&&!E(i.note,l))&&!(i.label&&!S(i.label,l))&&!(i.blockedBy&&!x(i.blockedBy,l))){if(i.blockedBy){let f=await u.validateBlockedBy(c,i.blockedBy);if(!f.valid){_(l,f.error);return}}try{let f={};if(i.title&&(f.title=i.title),i.status&&(f.status=i.status),i.priority&&(f.priority=i.priority),i.due!==void 0)if(i.due.toLowerCase()==="none")f.due_at=null;else{let k=dt(i.due,r.date_format,r.first_day_of_week,r.next_month);if(!k){_(l,`Could not parse due date: ${i.due}. Try: today, tomorrow, next friday, 2026-03-01, or march 15.`);return}f.due_at=k}if(i.assign!==void 0&&(i.assign.toLowerCase()==="none"||i.assign.trim()===""?f.owner=null:f.owner=i.assign),i.label||i.unlabel){let k=await u.get(c);if(!k){b(l,c);return}let h=[...k.labels];if(i.label)for(let T of i.label)h.includes(T)||h.push(T);if(i.unlabel&&(h=h.filter(T=>!i.unlabel.includes(T))),!S(h,l))return;f.labels=h}if(i.blockedBy||i.unblock){let k;if(i.blockedBy)k=[...i.blockedBy];else{let h=await u.get(c);if(!h){b(l,c);return}k=[...h.blocked_by]}if(i.unblock&&(k=k.filter(h=>!i.unblock.includes(h))),!x(k,l))return;f.blocked_by=k}if(i.repeat!==void 0)if(i.repeat.toLowerCase()==="none")f.recurrence=null;else try{f.recurrence=Fe(i.repeat)}catch(k){_(l,k instanceof Error?k.message:String(k));return}if(i.meta){let k=await u.get(c);if(!k){b(l,c);return}let h=jn(i.meta),T={...k.metadata};for(let[te,X]of Object.entries(h))X===null?delete T[te]:T[te]=X;if(!P(T,l))return;f.metadata=T}let y=Object.keys(f).length>0,v;if(i.clearNotes)v=await u.clearNotesAndUpdate(c,f,i.note);else if(i.note&&y)v=await u.updateWithNote(c,f,i.note);else if(i.note)v=await u.addNote(c,i.note);else if(y)v=await u.update(c,f);else{if(!l){e("No changes.");return}v=await u.get(c)}if(!v){b(l,c);return}if(e(l?V(v):Y(v)),v.status==="done"&&v.recurrence){let k=await u.getSpawnedTask(v.id);k&&(l?e(JSON.stringify({spawned:k},null,2)):(e(`
711
- ${w("Next occurrence:")} ${se(v.recurrence)}`),e(Y(k))))}}catch(f){if(f instanceof Q){d(f,l);return}throw f}}}),p.command("edit <id>").description("Open task in $EDITOR for complex edits").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
710
+ $ oru update 019414a3 -r "every monday"`).action(async(c,i)=>{let l=m(i);if(i.title!==void 0&&(i.title=Ve(i.title)),!(i.title!==void 0&&!N(i.title,l))&&!(i.note&&!E(i.note,l))&&!(i.label&&!S(i.label,l))&&!(i.blockedBy&&!O(i.blockedBy,l))){if(i.blockedBy){let f=await u.validateBlockedBy(c,i.blockedBy);if(!f.valid){_(l,f.error);return}}try{let f={};if(i.title&&(f.title=i.title),i.status&&(f.status=i.status),i.priority&&(f.priority=i.priority),i.due!==void 0)if(i.due.toLowerCase()==="none")f.due_at=null;else{let k=St(i.due,r.date_format,r.first_day_of_week,r.next_month);if(!k){_(l,`Could not parse due date: ${i.due}. Try: today, tomorrow, next friday, 2026-03-01, or march 15.`);return}f.due_at=k}if(i.assign!==void 0&&(i.assign.toLowerCase()==="none"||i.assign.trim()===""?f.owner=null:f.owner=i.assign),i.label||i.unlabel){let k=await u.get(c);if(!k){b(l,c);return}let h=[...k.labels];if(i.label)for(let T of i.label)h.includes(T)||h.push(T);if(i.unlabel&&(h=h.filter(T=>!i.unlabel.includes(T))),!S(h,l))return;f.labels=h}if(i.blockedBy||i.unblock){let k;if(i.blockedBy)k=[...i.blockedBy];else{let h=await u.get(c);if(!h){b(l,c);return}k=[...h.blocked_by]}if(i.unblock&&(k=k.filter(h=>!i.unblock.includes(h))),!O(k,l))return;f.blocked_by=k}if(i.repeat!==void 0)if(i.repeat.toLowerCase()==="none")f.recurrence=null;else try{f.recurrence=qe(i.repeat)}catch(k){_(l,k instanceof Error?k.message:String(k));return}if(i.meta){let k=await u.get(c);if(!k){b(l,c);return}let h=Vn(i.meta),T={...k.metadata};for(let[ne,X]of Object.entries(h))X===null?delete T[ne]:T[ne]=X;if(!F(T,l))return;f.metadata=T}let y=Object.keys(f).length>0,v;if(i.clearNotes)v=await u.clearNotesAndUpdate(c,f,i.note);else if(i.note&&y)v=await u.updateWithNote(c,f,i.note);else if(i.note)v=await u.addNote(c,i.note);else if(y)v=await u.update(c,f);else{if(!l){e("No changes.");return}v=await u.get(c)}if(!v){b(l,c);return}if(e(l?q(v):W(v)),v.status==="done"&&v.recurrence){let k=await u.getSpawnedTask(v.id);k&&(l?e(JSON.stringify({spawned:k},null,2)):(e(`
711
+ ${w("Next occurrence:")} ${ae(v.recurrence)}`),e(W(k))))}}catch(f){if(f instanceof Z){d(f,l);return}throw f}}}),p.command("edit <id>").description("Open task in $EDITOR for complex edits").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
712
712
  Examples:
713
713
  $ oru edit 019414a3
714
- $ EDITOR=nano oru edit 019414a3`).action(async(c,i)=>{let l=m(i);try{let f=await u.get(c);if(!f){b(l,c);return}let y=_n(f),{edited:v,tmpFile:k}=await bn(y),h,T,te;try{({fields:h,newNotes:T,removedNotes:te}=yn(v,f))}catch(U){let Nt=U instanceof Error?U.message:String(U);_(l,Nt),o(`Your edits are saved at: ${k}
715
- `);return}hn(k);let X=Object.keys(h).length>0,Xe=T.length>0;if(!X&&!Xe&&!te){e(l?V(f):"No changes.");return}if(h.title!==void 0&&(h.title=Ce(h.title)),h.title!==void 0&&!$(h.title,l))return;for(let U of T)if(!E(U,l))return;if(h.labels&&!S(h.labels,l)||h.blocked_by&&!x(h.blocked_by,l))return;if(h.blocked_by){let U=await u.validateBlockedBy(f.id,h.blocked_by);if(!U.valid){_(l,U.error);return}}if(h.metadata&&!P(h.metadata,l))return;let j;if(te){let Rt=[...v.slice(v.indexOf("+++",3)+3).split(`
716
- `).filter(qe=>qe.startsWith("- ")).map(qe=>qe.slice(2)),...T];Rt.length===0?j=await u.clearNotes(c):j=await u.replaceNotes(c,Rt),X&&(j=await u.update(c,h))}else if(Xe&&T.length===1&&X)j=await u.updateWithNote(c,h,T[0]);else if(Xe&&T.length===1&&!X)j=await u.addNote(c,T[0]);else{X&&(j=await u.update(c,h));for(let U of T)j=await u.addNote(c,U)}j||(j=await u.get(c)),e(l?V(j):Y(j))}catch(f){if(f instanceof Q){d(f,l);return}throw f}});function g(c,i,l,f){p.command(`${c} <id...>`).description(l).option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",f).action(async(y,v)=>{let k=m(v);for(let h of y)await O(h,k,()=>u.update(h,{status:i}),T=>{e(k?V(T):Y(T))})})}p.command("done <id...>").description("Mark one or more tasks as done (shortcut for update -s done)").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
714
+ $ EDITOR=nano oru edit 019414a3`).action(async(c,i)=>{let l=m(i);try{let f=await u.get(c);if(!f){b(l,c);return}let y=$n(f),{edited:v,tmpFile:k}=await Nn(y),h,T,ne;try{({fields:h,newNotes:T,removedNotes:ne}=Dn(v,f))}catch(Y){let Yt=Y instanceof Error?Y.message:String(Y);_(l,Yt),o(`Your edits are saved at: ${k}
715
+ `);return}Rn(k);let X=Object.keys(h).length>0,nt=T.length>0;if(!X&&!nt&&!ne){e(l?q(f):"No changes.");return}if(h.title!==void 0&&(h.title=Ve(h.title)),h.title!==void 0&&!N(h.title,l))return;for(let Y of T)if(!E(Y,l))return;if(h.labels&&!S(h.labels,l)||h.blocked_by&&!O(h.blocked_by,l))return;if(h.blocked_by){let Y=await u.validateBlockedBy(f.id,h.blocked_by);if(!Y.valid){_(l,Y.error);return}}if(h.metadata&&!F(h.metadata,l))return;let U;if(ne){let Bt=[...v.slice(v.indexOf("+++",3)+3).split(`
716
+ `).filter(rt=>rt.startsWith("- ")).map(rt=>rt.slice(2)),...T];Bt.length===0?U=await u.clearNotes(c):U=await u.replaceNotes(c,Bt),X&&(U=await u.update(c,h))}else if(nt&&T.length===1&&X)U=await u.updateWithNote(c,h,T[0]);else if(nt&&T.length===1&&!X)U=await u.addNote(c,T[0]);else{X&&(U=await u.update(c,h));for(let Y of T)U=await u.addNote(c,Y)}U||(U=await u.get(c)),e(l?q(U):W(U))}catch(f){if(f instanceof Z){d(f,l);return}throw f}});function g(c,i,l,f){p.command(`${c} <id...>`).description(l).option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",f).action(async(y,v)=>{let k=m(v);for(let h of y)await x(h,k,()=>u.update(h,{status:i}),T=>{e(k?q(T):W(T))})})}p.command("done <id...>").description("Mark one or more tasks as done (shortcut for update -s done)").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
717
717
  Examples:
718
718
  $ oru done 019414a3
719
- $ oru done 019414a3 019414b7`).action(async(c,i)=>{let l=m(i);for(let f of c)await O(f,l,()=>u.update(f,{status:"done"}),async y=>{if(e(l?V(y):Y(y)),y.recurrence){let v=await u.getSpawnedTask(y.id);v&&(l?e(JSON.stringify({spawned:v},null,2)):(e(`
720
- ${w("Next occurrence:")} ${se(y.recurrence)}`),e(Y(v))))}})}),g("start","in_progress","Start one or more tasks (shortcut for update -s in_progress)",`
719
+ $ oru done 019414a3 019414b7`).action(async(c,i)=>{let l=m(i);for(let f of c)await x(f,l,()=>u.update(f,{status:"done"}),async y=>{if(e(l?q(y):W(y)),y.recurrence){let v=await u.getSpawnedTask(y.id);v&&(l?e(JSON.stringify({spawned:v},null,2)):(e(`
720
+ ${w("Next occurrence:")} ${ae(y.recurrence)}`),e(W(v))))}})}),g("start","in_progress","Start one or more tasks (shortcut for update -s in_progress)",`
721
721
  Examples:
722
722
  $ oru start 019414a3
723
723
  $ oru start 019414a3 019414b7`),g("review","in_review","Mark one or more tasks as in_review (shortcut for update -s in_review)",`
@@ -727,23 +727,23 @@ Examples:
727
727
  Examples:
728
728
  $ oru context
729
729
  $ oru context --owner alice
730
- $ oru context -l backend`).action(async c=>{let{sections:i}=await u.getContext({owner:c.owner,label:c.label}),l=m(c);e(l?zt(i):Wt(i,new Date))}),p.command("delete <id...>").description("Delete one or more tasks permanently").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
730
+ $ oru context -l backend`).action(async c=>{let{sections:i}=await u.getContext({owner:c.owner,label:c.label}),l=m(c);e(l?un(i):tn(i,new Date))}),p.command("delete <id...>").description("Delete one or more tasks permanently").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
731
731
  Examples:
732
732
  $ oru delete 019414a3
733
- $ oru delete 019414a3 019414b7`).action(async(c,i)=>{let l=m(i);for(let f of c)await O(f,l,()=>u.delete(f),()=>{e(l?JSON.stringify({id:f,deleted:!0}):`Deleted ${f}`)})}),p.command("log <id>").description("Show change history of a task").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
733
+ $ oru delete 019414a3 019414b7`).action(async(c,i)=>{let l=m(i);for(let f of c)await x(f,l,()=>u.delete(f),()=>{e(l?JSON.stringify({id:f,deleted:!0}):`Deleted ${f}`)})}),p.command("log <id>").description("Show change history of a task").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
734
734
  Examples:
735
735
  $ oru log 019414a3
736
- $ oru log 019414a3 --json`).action(async(c,i)=>{let l=m(i);await O(c,l,()=>u.log(c),f=>{e(l?en(f):Yt(f))})}),p.command("sync <remote-path>").description("Sync with a filesystem remote").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
736
+ $ oru log 019414a3 --json`).action(async(c,i)=>{let l=m(i);await x(c,l,()=>u.log(c),f=>{e(l?pn(f):en(f))})}),p.command("sync <remote-path>").description("Sync with a filesystem remote").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
737
737
  Examples:
738
738
  $ oru sync /mnt/shared/oru
739
- $ oru sync ~/Dropbox/oru-sync`).action(async(c,i)=>{let l=new Me(c);try{let f=await rn(t,l,a),y=m(i);e(y?JSON.stringify(f,null,2):`Pushed ${f.pushed} ops, pulled ${f.pulled} ops.`)}catch(f){throw process.stderr.write(`Sync failed, database restored from backup.
740
- `),f}});let C=p.command("config").description("Manage configuration");C.command("init").description("Create a default config file with documented options").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=fe();if($t.existsSync(l)){e(i?JSON.stringify({message:"Config file already exists.",path:l}):`Config file already exists at ${l}`);return}$t.mkdirSync(we.dirname(l),{recursive:!0}),$t.writeFileSync(l,sn),e(i?JSON.stringify({message:"Config file created.",path:l}):`Created ${l}`)}),C.command("path").description("Print the config file path").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=fe();e(i?JSON.stringify({path:l}):l)});let J=p.command("filter").description("Manage saved list filters");J.command("list").description("List all saved filters").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=ce(),f=Object.keys(l);if(i){e(JSON.stringify({filters:f}));return}if(f.length===0){e("No saved filters. Use 'oru filter add <name> [flags]' to create one.");return}for(let y of f)e(y)}),J.command("show <name>").description("Show a filter's definition").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((c,i)=>{let l=m(i),y=ce()[c];if(!y){e(l?JSON.stringify({error:"not_found",name:c}):`Filter '${c}' not found.`),process.exitCode=1;return}if(l){e(JSON.stringify({name:c,...y}));return}let v=[`[${c}]`];for(let[k,h]of Object.entries(y))h!==void 0&&(Array.isArray(h)?v.push(`${k} = ${h.join(", ")}`):v.push(`${k} = ${h}`));e(v.join(`
741
- `))}),J.command("remove <name>").description("Delete a saved filter").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((c,i)=>{let l=m(i),f=ce();if(!(c in f)){e(l?JSON.stringify({error:"not_found",name:c}):`Filter '${c}' not found.`),process.exitCode=1;return}delete f[c],bt(f),e(l?JSON.stringify({message:`Removed filter '${c}'.`,name:c}):`Removed filter '${c}'.`)}),J.command("add <name>").description("Save a new named filter (accepts the same flags as 'oru list' plus --sql)").option("-s, --status <status>","Filter by status (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!D.includes(l))throw new Error(`Invalid status: ${l}. Allowed: ${D.join(", ")}`);return i.length===1?i[0]:i}).option("-p, --priority <priority>","Filter by priority (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!N.includes(l))throw new Error(`Invalid priority: ${l}. Allowed: ${N.join(", ")}`);return i.length===1?i[0]:i}).option("-l, --label <label>","Filter by label").option("--owner <owner>","Filter by owner").addOption(new z("--due <range>","Filter by due date").choices(["today","this-week"])).option("--overdue","Show only overdue tasks").addOption(new z("--sort <field>","Sort order").choices(B)).option("--search <query>","Search tasks by title").option("-a, --all","Include done tasks").option("--actionable","Show only tasks with no incomplete blockers").option("--limit <n>","Maximum number of tasks to return",Number).option("--offset <n>","Number of tasks to skip",Number).option("--sql <condition>",`Raw SQL WHERE condition (e.g. "priority = 'urgent'")`).option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
739
+ $ oru sync ~/Dropbox/oru-sync`).action(async(c,i)=>{let l=new We(c);try{let f=await _n(t,l,a),y=m(i);e(y?JSON.stringify(f,null,2):`Pushed ${f.pushed} ops, pulled ${f.pulled} ops.`)}catch(f){throw process.stderr.write(`Sync failed, database restored from backup.
740
+ `),f}});let M=p.command("config").description("Manage configuration");M.command("init").description("Create a default config file with documented options").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=ye();if(jt.existsSync(l)){e(i?JSON.stringify({message:"Config file already exists.",path:l}):`Config file already exists at ${l}`);return}jt.mkdirSync(Ie.dirname(l),{recursive:!0}),jt.writeFileSync(l,bn),e(i?JSON.stringify({message:"Config file created.",path:l}):`Created ${l}`)}),M.command("path").description("Print the config file path").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=ye();e(i?JSON.stringify({path:l}):l)});let H=p.command("filter").description("Manage saved list filters");H.command("list").description("List all saved filters").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=me(),f=Object.keys(l);if(i){e(JSON.stringify({filters:f}));return}if(f.length===0){e("No saved filters. Use 'oru filter add <name> [flags]' to create one.");return}for(let y of f)e(y)}),H.command("show <name>").description("Show a filter's definition").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((c,i)=>{let l=m(i),y=me()[c];if(!y){e(l?JSON.stringify({error:"not_found",name:c}):`Filter '${c}' not found.`),process.exitCode=1;return}if(l){e(JSON.stringify({name:c,...y}));return}let v=[`[${c}]`];for(let[k,h]of Object.entries(y))h!==void 0&&(Array.isArray(h)?v.push(`${k} = ${h.join(", ")}`):v.push(`${k} = ${h}`));e(v.join(`
741
+ `))}),H.command("remove <name>").description("Delete a saved filter").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((c,i)=>{let l=m(i),f=me();if(!(c in f)){e(l?JSON.stringify({error:"not_found",name:c}):`Filter '${c}' not found.`),process.exitCode=1;return}delete f[c],Dt(f),e(l?JSON.stringify({message:`Removed filter '${c}'.`,name:c}):`Removed filter '${c}'.`)}),H.command("add <name>").description("Save a new named filter (accepts the same flags as 'oru list' plus --sql)").option("-s, --status <status>","Filter by status (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!$.includes(l))throw new Error(`Invalid status: ${l}. Allowed: ${$.join(", ")}`);return i.length===1?i[0]:i}).option("-p, --priority <priority>","Filter by priority (comma-separated for multiple)",c=>{let i=c.split(",");for(let l of i)if(!D.includes(l))throw new Error(`Invalid priority: ${l}. Allowed: ${D.join(", ")}`);return i.length===1?i[0]:i}).option("-l, --label <label>","Filter by label").option("--owner <owner>","Filter by owner").addOption(new Q("--due <range>","Filter by due date").choices(["today","this-week"])).option("--overdue","Show only overdue tasks").addOption(new Q("--sort <field>","Sort order").choices(B)).option("--search <query>","Search tasks by title").option("-a, --all","Include done tasks").option("--actionable","Show only tasks with no incomplete blockers").option("--limit <n>","Maximum number of tasks to return",Number).option("--offset <n>","Number of tasks to skip",Number).option("--sql <condition>",`Raw SQL WHERE condition (e.g. "priority = 'urgent'")`).option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
742
742
  Examples:
743
743
  $ oru filter add mine --owner tchayen --status todo
744
744
  $ oru filter add upcoming --due this-week --sort due
745
- $ oru filter add edge --sql "priority = 'urgent'"`).action((c,i)=>{let l=m(i),f={};for(let h of yt)i[h]!==void 0&&(f[h]=i[h]);if(i.sql!==void 0&&(f.sql=i.sql),Object.keys(f).length===0){e(l?JSON.stringify({error:"validation",message:"No filter fields specified."}):"No filter fields specified. Pass at least one flag."),process.exitCode=1;return}let y=ce(),v=c in y;y[c]=f,bt(y);let k=v?`Updated filter '${c}'.`:`Saved filter '${c}'.`;e(l?JSON.stringify({message:k,name:c,filter:f}):k)}),!1;let F=p.command("completions").description("Generate shell completion scripts").action(async()=>{let c=ft();if(!c){e(`Could not detect shell from $SHELL.
745
+ $ oru filter add edge --sql "priority = 'urgent'"`).action((c,i)=>{let l=m(i),f={};for(let h of $t)i[h]!==void 0&&(f[h]=i[h]);if(i.sql!==void 0&&(f.sql=i.sql),Object.keys(f).length===0){e(l?JSON.stringify({error:"validation",message:"No filter fields specified."}):"No filter fields specified. Pass at least one flag."),process.exitCode=1;return}let y=me(),v=c in y;y[c]=f,Dt(y);let k=v?`Updated filter '${c}'.`:`Saved filter '${c}'.`;e(l?JSON.stringify({message:k,name:c,filter:f}):k)}),!1;let j=p.command("completions").description("Generate shell completion scripts").action(async()=>{let c=Et();if(!c){e(`Could not detect shell from $SHELL.
746
746
  Use: oru completions bash|zsh|fish`),process.exitCode=1;return}if(!process.stdin.isTTY){e(`Detected shell: ${c}
747
- Run interactively: oru completions ${c}`),process.exitCode=1;return}if(e(`Detected shell: ${c}`),!await gt(`Install completions for ${c}? [Y/n] `)){e("Aborted.");return}let l=Ue(c,e);e(Be(l))});for(let c of["bash","zsh","fish"])F.command(c).description(`Install ${c} completions`).option("--print","Print the completion script to stdout instead of installing").action(i=>{if(i.print||!process.stdout.isTTY){e(c==="bash"?ye():c==="zsh"?be():he());return}let l=Ue(c,e);e(Be(l))});p.command("self-update").description("Update oru to the latest version").option("--check","Only check if an update is available").action(async c=>{let{performUpdate:i}=await Promise.resolve().then(()=>(In(),Rn));await i(!!c.check)});let R=p.command("telemetry").description("Manage anonymous usage telemetry");return R.command("status").description("Show whether telemetry is enabled or disabled").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=kt(r);e(i?JSON.stringify({enabled:!l,...l?{reason:l}:{}}):l?`Telemetry: ${l}`:"Telemetry: enabled")}),R.command("enable").description("Enable anonymous usage telemetry").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c);ge("telemetry","true"),e(i?JSON.stringify({enabled:!0}):"Telemetry enabled.")}),R.command("disable").description("Disable anonymous usage telemetry").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c);ge("telemetry","false"),e(i?JSON.stringify({enabled:!1}):"Telemetry disabled.")}),p.command("backup [path]").description("Create a database backup snapshot").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((c,i)=>{let l=m(i),f=c??r.backup_path;if(!f){e(l?JSON.stringify({error:"validation",message:"No backup path specified."}):"No backup path specified. Pass a path argument or set backup_path in config."),process.exitCode=1;return}let y=Je(t,f);e(l?JSON.stringify({path:y}):`Backed up to ${y}`)}),p.command("mcp").description("Start the MCP (Model Context Protocol) server over stdio").action(async()=>{let c=we.dirname(Dt(import.meta.url)),i=we.join(c,"mcp","index.js"),l=Fn(process.execPath,[i],{stdio:"inherit",env:process.env});await new Promise(f=>{l.on("exit",y=>{y!==null&&(process.exitCode=y),f()}),l.on("error",y=>{process.stderr.write(`Failed to start MCP server: ${y.message}
748
- `),process.exitCode=1,f()})})}),p.command("_complete <type> [prefix]",{hidden:!0}).action(async(c,i)=>{let l=await pt(u,c,i??"");l.length>0&&e(l.join(`
749
- `))}),Bn(p),p}async function ko(){let t=Date.now(),e=Xt();Kt(e);let n=ct(),o=ho(e,void 0,n);if(n.backup_path){let{autoBackup:a}=await Promise.resolve().then(()=>(vt(),xn));a(e,n.backup_path,n.backup_interval)}try{let{showFirstRunNotice:a}=await Promise.resolve().then(()=>(We(),St));a(n)}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Telemetry notice failed:",a)}let r;try{let{checkForUpdate:a}=await Promise.resolve().then(()=>(Ve(),Ot));r=a(n)}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Update check failed:",a)}let s;try{await o.parseAsync(process.argv)}catch(a){if(a instanceof Error&&"exitCode"in a)process.exitCode=a.exitCode,s="code"in a?String(a.code):"commander.error";else throw a}finally{e.close()}try{let{extractCommandAndFlags:a,buildEvent:u,sendEvent:p}=await Promise.resolve().then(()=>(We(),St)),{command:m,flags:d}=a(process.argv);if(ht(n)&&!m.startsWith("telemetry")){let _=Date.now()-t,b=u(m,d,_,Number(process.exitCode??0),s);p(b)}}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",a)}if(r)try{let a=await r;if(a){let{printUpdateNotice:u}=await Promise.resolve().then(()=>(Ve(),Ot));u(a)}}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Update check failed:",a)}}var So=Dt(import.meta.url),vo=process.argv[1]&&So===process.argv[1];vo&&ko().catch(t=>{process.env.ORU_DEBUG==="1"?console.error(t):console.error(t instanceof Error?t.message:String(t)),process.exit(1)});export{ho as createProgram};
747
+ Run interactively: oru completions ${c}`),process.exitCode=1;return}if(e(`Detected shell: ${c}`),!await xt(`Install completions for ${c}? [Y/n] `)){e("Aborted.");return}let l=Ke(c,e);e(Ge(l))});for(let c of["bash","zsh","fish"])j.command(c).description(`Install ${c} completions`).option("--print","Print the completion script to stdout instead of installing").action(i=>{if(i.print||!process.stdout.isTTY){e(c==="bash"?Ee():c==="zsh"?xe():Oe());return}let l=Ke(c,e);e(Ge(l))});p.command("self-update").description("Update oru to the latest version").option("--check","Only check if an update is available").action(async c=>{let{performUpdate:i}=await Promise.resolve().then(()=>(Jn(),Wn));await i(!!c.check)});let R=p.command("telemetry").description("Manage anonymous usage telemetry");return R.command("status").description("Show whether telemetry is enabled or disabled").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c),l=Rt(r);e(i?JSON.stringify({enabled:!l,...l?{reason:l}:{}}):l?`Telemetry: ${l}`:"Telemetry: enabled")}),R.command("enable").description("Enable anonymous usage telemetry").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c);be("telemetry","true"),e(i?JSON.stringify({enabled:!0}):"Telemetry enabled.")}),R.command("disable").description("Disable anonymous usage telemetry").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(c=>{let i=m(c);be("telemetry","false"),e(i?JSON.stringify({enabled:!1}):"Telemetry disabled.")}),p.command("backup [path]").description("Create a database backup snapshot").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((c,i)=>{let l=m(i),f=c??r.backup_path;if(!f){e(l?JSON.stringify({error:"validation",message:"No backup path specified."}):"No backup path specified. Pass a path argument or set backup_path in config."),process.exitCode=1;return}let y=Ze(t,f);e(l?JSON.stringify({path:y}):`Backed up to ${y}`)}),p.command("mcp").description("Start the MCP (Model Context Protocol) server over stdio").action(async()=>{let c=Ie.dirname(Ut(import.meta.url)),i=Ie.join(c,"mcp","index.js"),l=Hn(process.execPath,[i],{stdio:"inherit",env:process.env});await new Promise(f=>{l.on("exit",y=>{y!==null&&(process.exitCode=y),f()}),l.on("error",y=>{process.stderr.write(`Failed to start MCP server: ${y.message}
748
+ `),process.exitCode=1,f()})})}),p.command("_complete <type> [prefix]",{hidden:!0}).action(async(c,i)=>{let l=await wt(u,c,i??"");l.length>0&&e(l.join(`
749
+ `))}),Xn(p),p}async function $o(){let t=Date.now(),e=sn();ln(e);let n=ht(),o=Oo(e,void 0,n);if(n.backup_path){let{autoBackup:a}=await Promise.resolve().then(()=>(At(),jn));a(e,n.backup_path,n.backup_interval)}try{let{showFirstRunNotice:a}=await Promise.resolve().then(()=>(Qe(),It));a(n)}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Telemetry notice failed:",a)}let r;try{let{checkForUpdate:a}=await Promise.resolve().then(()=>(tt(),Ct));r=a(n)}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Update check failed:",a)}let s;try{await o.parseAsync(process.argv)}catch(a){if(a instanceof Error&&"exitCode"in a)process.exitCode=a.exitCode,s="code"in a?String(a.code):"commander.error";else throw a}finally{e.close()}try{let{extractCommandAndFlags:a,buildEvent:u,sendEvent:p}=await Promise.resolve().then(()=>(Qe(),It)),{command:m,flags:d}=a(process.argv);if(Nt(n)&&!m.startsWith("telemetry")){let _=Date.now()-t,b=u(m,d,_,Number(process.exitCode??0),s);p(b)}}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",a)}if(r)try{let a=await r;if(a){let{printUpdateNotice:u}=await Promise.resolve().then(()=>(tt(),Ct));u(a)}}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Update check failed:",a)}}var Do=Ut(import.meta.url),No=process.argv[1]&&Do===process.argv[1];No&&$o().catch(t=>{process.env.ORU_DEBUG==="1"?console.error(t):console.error(t instanceof Error?t.message:String(t)),process.exit(1)});export{Oo as createProgram};
package/dist/mcp/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{StdioServerTransport as Ie}from"@modelcontextprotocol/sdk/server/stdio.js";import se from"better-sqlite3";import W from"path";import ie from"os";import N from"fs";function ae(){return process.env.ORU_DB_PATH?process.env.ORU_DB_PATH:W.join(ie.homedir(),".oru","oru.db")}function j(r){let t=r??ae(),e=W.dirname(t);N.existsSync(e)||N.mkdirSync(e,{recursive:!0,mode:448});let s=new se(t);s.pragma("journal_mode = WAL"),s.pragma("foreign_keys = ON");try{N.chmodSync(t,384)}catch{}return s}function oe(r){let t=r.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();return t?parseInt(t.value,10):0}function J(r,t){let e=oe(r),s=t.filter(a=>a.version>e).sort((a,l)=>a.version-l.version);if(s.length===0)return 0;let n=s[s.length-1].version;return process.stderr.write(`Migrating database from v${e} to v${n}...
3
- `),r.transaction(()=>{for(let a of s)a.up(r),r.prepare("UPDATE meta SET value = ? WHERE key = 'schema_version'").run(String(a.version));return s.length})()}function X(r){r.exec(`
2
+ import{StdioServerTransport as He}from"@modelcontextprotocol/sdk/server/stdio.js";import Se from"better-sqlite3";import V from"path";import we from"os";import M from"fs";function Ee(){return process.env.ORU_DB_PATH?process.env.ORU_DB_PATH:V.join(we.homedir(),".oru","oru.db")}function G(r){let t=r??Ee(),e=V.dirname(t);M.existsSync(e)||M.mkdirSync(e,{recursive:!0,mode:448});let s=new Se(t);s.pragma("journal_mode = WAL"),s.pragma("foreign_keys = ON");try{M.chmodSync(t,384)}catch{}return s}function xe(r){let t=r.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();return t?parseInt(t.value,10):0}function Q(r,t){let e=xe(r),s=t.filter(i=>i.version>e).sort((i,o)=>i.version-o.version);if(s.length===0)return 0;let n=s[s.length-1].version;return process.stderr.write(`Migrating database from v${e} to v${n}...
3
+ `),r.transaction(()=>{for(let i of s)i.up(r),r.prepare("UPDATE meta SET value = ? WHERE key = 'schema_version'").run(String(i.version));return s.length})()}function z(r){r.exec(`
4
4
  CREATE TABLE IF NOT EXISTS tasks (
5
5
  id TEXT PRIMARY KEY,
6
6
  title TEXT NOT NULL,
@@ -30,8 +30,8 @@ import{StdioServerTransport as Ie}from"@modelcontextprotocol/sdk/server/stdio.js
30
30
  );
31
31
 
32
32
  INSERT OR IGNORE INTO meta (key, value) VALUES ('schema_version', '1');
33
- `),J(r,le)}var le=[{version:2,up:r=>{r.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_id ON oplog(task_id)"),r.exec("CREATE INDEX IF NOT EXISTS idx_oplog_device_id ON oplog(device_id)")}},{version:3,up:r=>{r.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_timestamp ON oplog(task_id, timestamp, id)")}},{version:4,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN due_at TEXT")}},{version:5,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN blocked_by TEXT NOT NULL DEFAULT '[]'")}},{version:6,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN owner TEXT")}},{version:7,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN recurrence TEXT")}}];import{Kysely as ce,SqliteDialect as ue}from"kysely";function q(r){return new ce({dialect:new ue({database:r})})}import{randomBytes as de}from"crypto";var K="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",Je=new Set(K),pe=11;function R(r,t){let e=0n;for(let n of r)e=e<<8n|BigInt(n);let s=[];for(let n=0;n<t;n++)s.push(K[Number(e%62n)]),e/=62n;return s.reverse().join("")}function T(){return R(de(8),pe)}function H(r){let t=r.prepare("SELECT value FROM meta WHERE key = 'device_id'").get();if(t)return t.value;let e=T();return r.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('device_id', ?)").run(e),r.prepare("SELECT value FROM meta WHERE key = 'device_id'").get().value}import{sql as Ee}from"kysely";import{sql as f}from"kysely";var h=["todo","in_progress","in_review","done"],ge=new Set(h),_=["low","medium","high","urgent"],fe=new Set(_);var V="todo",Q="medium";var L=class extends Error{prefix;matches;constructor(t,e){super(`Prefix '${t}' is ambiguous, matches: ${e.join(", ")}`),this.name="AmbiguousPrefixError",this.prefix=t,this.matches=e}};function w(r,t){try{return JSON.parse(r)}catch{return t}}function $(r){return{id:r.id,title:r.title,status:r.status,priority:r.priority,owner:r.owner,due_at:r.due_at,recurrence:r.recurrence,blocked_by:w(r.blocked_by,[]),labels:w(r.labels,[]),notes:w(r.notes,[]),metadata:w(r.metadata,{}),created_at:r.created_at,updated_at:r.updated_at,deleted_at:r.deleted_at}}async function A(r,t,e){let s=t.id??T(),n=e??new Date().toISOString(),i={id:s,title:t.title,status:t.status??V,priority:t.priority??Q,owner:t.owner??null,due_at:t.due_at??null,recurrence:t.recurrence??null,blocked_by:t.blocked_by??[],labels:t.labels??[],notes:t.notes??[],metadata:t.metadata??{},created_at:n,updated_at:n,deleted_at:null};return await r.insertInto("tasks").values({id:i.id,title:i.title,status:i.status,priority:i.priority,owner:i.owner,due_at:i.due_at,recurrence:i.recurrence,blocked_by:JSON.stringify(i.blocked_by),labels:JSON.stringify(i.labels),notes:JSON.stringify(i.notes),metadata:JSON.stringify(i.metadata),created_at:i.created_at,updated_at:i.updated_at,deleted_at:i.deleted_at}).execute(),i}async function S(r,t){let e=r.selectFrom("tasks").selectAll().where("deleted_at","is",null);if(t?.status&&(Array.isArray(t.status)?e=e.where("status","in",t.status):e=e.where("status","=",t.status)),t?.priority&&(Array.isArray(t.priority)?e=e.where("priority","in",t.priority):e=e.where("priority","=",t.priority)),t?.owner&&(e=e.where("owner","=",t.owner)),t?.label){let i=t.label;e=e.where(f`EXISTS (SELECT 1 FROM json_each(labels) WHERE json_each.value = ${i})`)}if(t?.search){let i=t.search.replace(/[\\%_]/g,"\\$&");e=e.where(f`title LIKE '%' || ${i} || '%' ESCAPE '\\' COLLATE NOCASE`)}switch(t?.actionable&&(e=e.where("status","!=","done").where(f`NOT EXISTS (
33
+ `),Q(r,De)}var De=[{version:2,up:r=>{r.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_id ON oplog(task_id)"),r.exec("CREATE INDEX IF NOT EXISTS idx_oplog_device_id ON oplog(device_id)")}},{version:3,up:r=>{r.exec("CREATE INDEX IF NOT EXISTS idx_oplog_task_timestamp ON oplog(task_id, timestamp, id)")}},{version:4,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN due_at TEXT")}},{version:5,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN blocked_by TEXT NOT NULL DEFAULT '[]'")}},{version:6,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN owner TEXT")}},{version:7,up:r=>{r.exec("ALTER TABLE tasks ADD COLUMN recurrence TEXT")}}];import{Kysely as ve,SqliteDialect as Ie}from"kysely";function Z(r){return new ve({dialect:new Ie({database:r})})}import{randomBytes as Oe}from"crypto";var ee="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",dt=new Set(ee),Ne=11;function U(r,t){let e=0n;for(let n of r)e=e<<8n|BigInt(n);let s=[];for(let n=0;n<t;n++)s.push(ee[Number(e%62n)]),e/=62n;return s.reverse().join("")}function h(){return U(Oe(8),Ne)}function te(r){let t=r.prepare("SELECT value FROM meta WHERE key = 'device_id'").get();if(t)return t.value;let e=h();return r.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('device_id', ?)").run(e),r.prepare("SELECT value FROM meta WHERE key = 'device_id'").get().value}import{sql as je}from"kysely";import{sql as m}from"kysely";var _=["todo","in_progress","in_review","done"],Le=new Set(_),S=["low","medium","high","urgent"],Ae=new Set(S),re=["title","status","priority","owner","due_at","recurrence","blocked_by","labels","metadata"],Re=new Set(re);var ne="todo",se="medium";var F=class extends Error{prefix;matches;constructor(t,e){super(`Prefix '${t}' is ambiguous, matches: ${e.join(", ")}`),this.name="AmbiguousPrefixError",this.prefix=t,this.matches=e}};function w(r,t){try{return JSON.parse(r)}catch{return t}}function P(r){return{id:r.id,title:r.title,status:r.status,priority:r.priority,owner:r.owner,due_at:r.due_at,recurrence:r.recurrence,blocked_by:w(r.blocked_by,[]),labels:w(r.labels,[]),notes:w(r.notes,[]),metadata:w(r.metadata,{}),created_at:r.created_at,updated_at:r.updated_at,deleted_at:r.deleted_at}}async function B(r,t,e){let s=t.id??h(),n=e??new Date().toISOString(),a={id:s,title:t.title,status:t.status??ne,priority:t.priority??se,owner:t.owner??null,due_at:t.due_at??null,recurrence:t.recurrence??null,blocked_by:t.blocked_by??[],labels:t.labels??[],notes:t.notes??[],metadata:t.metadata??{},created_at:n,updated_at:n,deleted_at:null};return await r.insertInto("tasks").values({id:a.id,title:a.title,status:a.status,priority:a.priority,owner:a.owner,due_at:a.due_at,recurrence:a.recurrence,blocked_by:JSON.stringify(a.blocked_by),labels:JSON.stringify(a.labels),notes:JSON.stringify(a.notes),metadata:JSON.stringify(a.metadata),created_at:a.created_at,updated_at:a.updated_at,deleted_at:a.deleted_at}).execute(),a}async function E(r,t){let e=r.selectFrom("tasks").selectAll().where("deleted_at","is",null);if(t?.status&&(Array.isArray(t.status)?e=e.where("status","in",t.status):e=e.where("status","=",t.status)),t?.priority&&(Array.isArray(t.priority)?e=e.where("priority","in",t.priority):e=e.where("priority","=",t.priority)),t?.owner&&(e=e.where("owner","=",t.owner)),t?.label){let a=t.label;e=e.where(m`EXISTS (SELECT 1 FROM json_each(labels) WHERE json_each.value = ${a})`)}if(t?.search){let a=t.search.replace(/[\\%_]/g,"\\$&");e=e.where(m`title LIKE '%' || ${a} || '%' ESCAPE '\\' COLLATE NOCASE`)}switch(t?.actionable&&(e=e.where("status","!=","done").where(m`NOT EXISTS (
34
34
  SELECT 1 FROM json_each(tasks.blocked_by) AS dep
35
35
  JOIN tasks AS blocker ON blocker.id = dep.value
36
36
  WHERE blocker.status != 'done' AND blocker.deleted_at IS NULL
37
- )`)),t?.sql&&(e=e.where(f`(${f.raw(t.sql)})`)),t?.sort??"priority"){case"due":e=e.orderBy(f`CASE WHEN due_at IS NULL THEN 1 ELSE 0 END`,"asc").orderBy("due_at","asc").orderBy("created_at","asc");break;case"title":e=e.orderBy(f`title COLLATE NOCASE`,"asc").orderBy("created_at","asc");break;case"created":e=e.orderBy("created_at","asc");break;default:e=e.orderBy(f`CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`).orderBy("created_at","asc");break}return(t?.limit!==void 0||t?.offset!==void 0)&&(e=e.limit(t.limit??-1)),t?.offset!==void 0&&(e=e.offset(t.offset)),(await e.execute()).map($)}function G(r,t){return t.all||t.status!==void 0?r:r.filter(e=>e.status!=="done")}async function p(r,t){let e=await r.selectFrom("tasks").selectAll().where("id","=",t).where("deleted_at","is",null).executeTakeFirst();if(e)return $(e);if(!t)return null;let s=t.replace(/[\\%_]/g,"\\$&"),n=await r.selectFrom("tasks").selectAll().where(f`id LIKE ${s} || '%' ESCAPE '\\'`).where("deleted_at","is",null).execute();if(n.length===1)return $(n[0]);if(n.length>1)throw new L(t,n.map(i=>i.id));return null}async function E(r,t,e,s){let n=await p(r,t);if(!n)return null;let a={updated_at:s??new Date().toISOString()};return e.title!==void 0&&(a.title=e.title),e.status!==void 0&&(a.status=e.status),e.priority!==void 0&&(a.priority=e.priority),e.owner!==void 0&&(a.owner=e.owner),e.due_at!==void 0&&(a.due_at=e.due_at),e.recurrence!==void 0&&(a.recurrence=e.recurrence),e.blocked_by!==void 0&&(a.blocked_by=JSON.stringify(e.blocked_by)),e.labels!==void 0&&(a.labels=JSON.stringify(e.labels)),e.metadata!==void 0&&(a.metadata=JSON.stringify(e.metadata)),await r.updateTable("tasks").set(a).where("id","=",n.id).execute(),p(r,n.id)}async function D(r,t,e,s){let n=await p(r,t);if(!n)return null;let i=e.trim();if(i.length===0||n.notes.some(u=>u.trim()===i))return n;let a=[...n.notes,i],l=s??new Date().toISOString();return await r.updateTable("tasks").set({notes:JSON.stringify(a),updated_at:l}).where("id","=",n.id).execute(),p(r,n.id)}async function x(r,t,e,s){let n=await p(r,t);if(!n)return null;let i=s??new Date().toISOString();return await r.updateTable("tasks").set({notes:JSON.stringify(e),updated_at:i}).where("id","=",n.id).execute(),p(r,n.id)}async function z(r,t,e){let s=await p(r,t);if(!s)return!1;let n=e??new Date().toISOString(),i=await r.updateTable("tasks").set({deleted_at:n,updated_at:n}).where("id","=",s.id).where("deleted_at","is",null).executeTakeFirst();return BigInt(i.numUpdatedRows)>0n}async function g(r,t,e){let s=T(),n=e??new Date().toISOString();return await r.insertInto("oplog").values({id:s,task_id:t.task_id,device_id:t.device_id,op_type:t.op_type,field:t.field,value:t.value,timestamp:n}).execute(),{id:s,task_id:t.task_id,device_id:t.device_id,op_type:t.op_type,field:t.field,value:t.value,timestamp:n}}function ye(){return"NO_COLOR"in process.env?!1:"FORCE_COLOR"in process.env?!0:process.stdout.isTTY??!1}function I(r,t){let e=`\x1B[${r}m`,s=`\x1B[${t}m`;return n=>ye()?`${e}${n}${s}`:n}var me=I(1,22),be=I(2,22),Te=I(3,23),ke=I(37,39);function M(r,t){let e=t??new Date,s=new Date(Number(r.slice(0,4)),Number(r.slice(5,7))-1,Number(r.slice(8,10)),Number(r.slice(11,13))||0,Number(r.slice(14,16))||0);return r.slice(11,16)==="00:00"&&s.setDate(s.getDate()+1),s<e}function Z(r,t){if(M(r,t))return!1;let e=t??new Date,s=new Date(Number(r.slice(0,4)),Number(r.slice(5,7))-1,Number(r.slice(8,10)),Number(r.slice(11,13))||0,Number(r.slice(14,16))||0);return r.slice(11,16)==="00:00"&&s.setDate(s.getDate()+1),(s.getTime()-e.getTime())/(1e3*60*60)<=48}var he={SU:0,MO:1,TU:2,WE:3,TH:4,FR:5,SA:6};function _e(r){let t=r.split(";"),e="",s=1,n=null,i=null;for(let a of t){let[l,u]=a.split("=");switch(l){case"FREQ":e=u;break;case"INTERVAL":s=Number(u);break;case"BYDAY":n=u.split(",");break;case"BYMONTHDAY":i=Number(u);break}}return{freq:e,interval:s,byDay:n,byMonthDay:i}}function v(r,t){let e=new Date(r);return e.setDate(e.getDate()+t),e}function U(r,t){let e=new Date(r),s=e.getMonth()+t;return e.setMonth(s),e.getMonth()!==(s%12+12)%12&&e.setDate(0),e}function ee(r,t){let e=_e(r);switch(e.freq){case"DAILY":return v(t,e.interval);case"WEEKLY":{if(e.byDay&&e.byDay.length>0){let s=e.byDay.map(l=>he[l]).sort((l,u)=>l-u),n=t.getDay();for(let l of s)if(l>n)return v(t,l-n);let i=7-n+s[0],a=(e.interval-1)*7;return v(t,i+a)}return v(t,e.interval*7)}case"MONTHLY":{if(e.byMonthDay!==null){let s=e.byMonthDay,n=new Date(t);if(t.getDate()<s){if(n.setDate(s),n.getMonth()!==t.getMonth()&&(n=new Date(t.getFullYear(),t.getMonth()+1,0)),n.getTime()<=t.getTime()){n=U(t,e.interval);let i=n.getMonth();n.setDate(s),n.getMonth()!==i&&(n=new Date(n.getFullYear(),i+1,0))}}else{n=U(t,e.interval);let i=n.getMonth();n.setDate(s),n.getMonth()!==i&&(n=new Date(n.getFullYear(),i+1,0))}return n}return U(t,e.interval)}case"YEARLY":{let s=new Date(t);return s.setFullYear(s.getFullYear()+e.interval),s.getMonth()!==t.getMonth()&&s.setDate(0),s}default:throw new Error(`Unsupported FREQ: ${e.freq}`)}}import{createHash as we}from"crypto";var Se="oru-recurrence";function F(r){let t=we("sha256").update(`${Se}:${r}`).digest();return R(t.subarray(0,8),11)}function P(r){return r===null?null:typeof r=="string"?r:JSON.stringify(r)}function te(r){return{title:r.title,status:r.status,priority:r.priority,owner:r.owner,due_at:r.due_at,recurrence:r.recurrence,blocked_by:r.blocked_by,labels:r.labels,notes:r.notes,metadata:r.metadata}}var O=class{constructor(t,e){this.db=t;this.deviceId=e}async add(t){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n={...t,owner:t.owner||null},i=await A(e,n,s);return await g(e,{task_id:i.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(te(i))},s),i})}async _maybeSpawn(t,e,s){if(e.status!=="done"||!e.recurrence)return null;let n=F(e.id);if(await p(t,n))return null;let a=e.recurrence,l=a.startsWith("after:");l&&(a=a.slice(6));let u;l?u=new Date(s):e.due_at?u=new Date(e.due_at):u=new Date(s);let d=ee(a,u),c=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}T${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}:${String(d.getSeconds()).padStart(2,"0")}`,k={id:n,title:e.title,priority:e.priority,owner:e.owner,due_at:c,recurrence:e.recurrence,labels:[...e.labels],metadata:{...e.metadata}},y=await A(t,k,s);return await g(t,{task_id:y.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(te(y))},s),y}async getSpawnedTask(t){let e=F(t);return p(this.db,e)}async validateBlockedBy(t,e){let s=null;if(t!==null){let i=await p(this.db,t);if(!i)return{valid:!1,error:`Task "${t}" not found.`};s=i.id}let n=[];for(let i of e){let a=await p(this.db,i);if(!a)return{valid:!1,error:`Task "${i}" not found.`};if(s!==null&&a.id===s)return{valid:!1,error:"A task cannot block itself."};n.push(a.id)}if(s!==null&&n.length>0){let i=await S(this.db),a=new Map(i.map(l=>[l.id,l]));for(let l of n){let u=[l],d=new Set;for(;u.length>0;){let c=u.shift();if(c===s)return{valid:!1,error:`Setting blocked_by to "${l}" would create a circular dependency.`};if(d.has(c))continue;d.add(c);let k=a.get(c);if(k)for(let y of k.blocked_by)d.has(y)||u.push(y)}}}return{valid:!0}}async list(t){return S(this.db,t)}async get(t){return p(this.db,t)}async update(t,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),i=await E(s,t,e,n);if(!i)return null;for(let[a,l]of Object.entries(e))a==="note"||l===void 0||await g(s,{task_id:i.id,device_id:this.deviceId,op_type:"update",field:a,value:P(l)},n);return await this._maybeSpawn(s,i,n),i})}async addNote(t,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),i=await p(s,t);if(!i)return null;let a=e.trim();if(a.length===0||i.notes.some(u=>u.trim()===a))return i;let l=await D(s,i.id,a,n);return await g(s,{task_id:i.id,device_id:this.deviceId,op_type:"update",field:"notes",value:a},n),l})}async updateWithNote(t,e,s){return this.db.transaction().execute(async n=>{let i=new Date().toISOString(),a=await E(n,t,e,i);if(!a)return null;let l=a.id;for(let[d,c]of Object.entries(e))d==="note"||c===void 0||await g(n,{task_id:l,device_id:this.deviceId,op_type:"update",field:d,value:P(c)},i);let u=s.trim();return u.length>0&&!a.notes.some(d=>d.trim()===u)&&(a=await D(n,l,u,i),await g(n,{task_id:l,device_id:this.deviceId,op_type:"update",field:"notes",value:u},i)),await this._maybeSpawn(n,a,i),a})}async clearNotes(t){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n=await x(e,t,[],s);return n?(await g(e,{task_id:n.id,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},s),n):null})}async clearNotesAndUpdate(t,e,s){return this.db.transaction().execute(async n=>{let i=new Date().toISOString(),a=await x(n,t,[],i);if(!a)return null;let l=a.id;if(await g(n,{task_id:l,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},i),s){let d=s.trim();d.length>0&&(a=await D(n,l,d,i),await g(n,{task_id:l,device_id:this.deviceId,op_type:"update",field:"notes",value:d},i))}if(Object.keys(e).length>0){a=await E(n,l,e,i);for(let[d,c]of Object.entries(e))d==="note"||c===void 0||await g(n,{task_id:l,device_id:this.deviceId,op_type:"update",field:d,value:P(c)},i)}return await this._maybeSpawn(n,a,i),a})}async replaceNotes(t,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),i=await x(s,t,e,n);if(!i)return null;let a=i.id;await g(s,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},n);for(let l of e)await g(s,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes",value:l},n);return i})}async listLabels(){let t=await S(this.db),e=new Set;for(let s of t)for(let n of s.labels)e.add(n);return[...e].sort()}async getContext(t){let e=new Date,s=await this.list({sort:"priority",owner:t?.owner,label:t?.label}),n=await this.list({status:"done",sort:"priority",owner:t?.owner,label:t?.label}),i={overdue:[],due_soon:[],in_progress:[],actionable:[],blocked:[],recently_completed:[]},a=new Set(s.filter(c=>c.status!=="done").map(c=>c.id)),l=new Date(e.getTime()-1440*60*1e3).toISOString();for(let c of n)c.updated_at>=l&&i.recently_completed.push(c);for(let c of s){if(c.status==="done")continue;if(c.status==="in_progress"||c.status==="in_review"){i.in_progress.push(c);continue}if(c.due_at&&M(c.due_at,e)){i.overdue.push(c);continue}if(c.due_at&&Z(c.due_at,e)){i.due_soon.push(c);continue}if(c.blocked_by.some(y=>a.has(y))){i.blocked.push(c);continue}if(c.status==="todo"){i.actionable.push(c);continue}}let u=new Map;for(let c of[...s,...n])u.set(c.id,c.title);i.blockerTitles=u;let d={overdue:i.overdue.length,due_soon:i.due_soon.length,in_progress:i.in_progress.length,actionable:i.actionable.length,blocked:i.blocked.length,recently_completed:i.recently_completed.length};return{sections:i,summary:d}}async log(t){let e=await p(this.db,t);return e?await this.db.selectFrom("oplog").selectAll().where("task_id","=",e.id).orderBy("timestamp","asc").orderBy(Ee`rowid`,"asc").execute():null}async delete(t){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n=await p(e,t);if(!n)return!1;let i=await z(e,n.id,s);return i&&await g(e,{task_id:n.id,device_id:this.deviceId,op_type:"delete",field:null,value:null},s),i})}};import{McpServer as De}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as o}from"zod";function m(r){let{deleted_at:t,...e}=r;return e}var re="0.0.1";var B=o.enum(h),C=o.enum(_);function xe(r){return r instanceof Error&&"code"in r&&typeof r.code=="string"&&r.code.startsWith("SQLITE_")}function b(r){return xe(r)||r instanceof TypeError?"An internal error occurred. Please try again.":r instanceof Error?r.message:"An internal error occurred. Please try again."}function ne(r){let t=re,e=new De({name:"oru",version:t},{capabilities:{logging:{}}});return e.registerTool("add_task",{title:"Add task",description:"Create a new task. Returns the created task. Defaults to status 'todo' and priority 'medium' if not specified. Pass an 'id' field to enable idempotent creates - if a task with that ID already exists, the existing task is returned instead of creating a duplicate.",inputSchema:o.object({title:o.string().describe("Task title, e.g. 'Fix login bug'"),id:o.string().optional().describe("Custom task ID for idempotent creates. If a task with this ID already exists, the existing task is returned. Must be a 11-character base62 string (alphabet: 0-9, A-Z, a-z)."),status:B.optional().describe("Initial status. Valid values: todo, in_progress, in_review, done. Defaults to 'todo'."),priority:C.optional().describe("Priority level. Valid values: low, medium, high, urgent. Defaults to 'medium'."),owner:o.string().optional().describe("Assign to owner, e.g. 'alice'"),due_at:o.string().optional().describe("Due date as ISO 8601 datetime string, e.g. '2026-03-01T00:00:00.000Z'"),blocked_by:o.array(o.string()).optional().describe("Array of task IDs that must be completed before this task, e.g. ['0196b8e0-...']"),labels:o.array(o.string()).optional().describe("Array of string labels to attach, e.g. ['bug', 'frontend']"),notes:o.array(o.string()).optional().describe("Initial notes to add to the task, e.g. ['Started migration']"),recurrence:o.string().nullable().optional().describe("Recurrence rule in RRULE format, e.g. 'FREQ=DAILY', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'. Prefix with 'after:' for completion-based recurrence (next due computed from completion time instead of current due date), e.g. 'after:FREQ=WEEKLY'. Set to null to remove recurrence."),metadata:o.record(o.string(),o.unknown()).optional().describe("Arbitrary JSON object for storing custom key-value data, e.g. {pr: 42}")})},async s=>{try{let n=await r.add(s);return{content:[{type:"text",text:JSON.stringify(m(n),null,2)}]}}catch(n){let i=n instanceof Error?n.message:String(n);if(s.id&&i.includes("UNIQUE constraint")){let a=await r.get(s.id);if(a)return{content:[{type:"text",text:JSON.stringify(m(a),null,2)}]}}return{content:[{type:"text",text:b(n)}],isError:!0}}}),e.registerTool("update_task",{title:"Update task",description:"Update fields on an existing task. Only send the fields you want to change - omitted fields are left unchanged. Notes are append-only: use the 'note' field to add a new note without affecting existing ones. Returns the updated task.",inputSchema:o.object({id:o.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID"),title:o.string().optional().describe("New title"),status:B.optional().describe("New status. Valid values: todo, in_progress, in_review, done."),priority:C.optional().describe("New priority. Valid values: low, medium, high, urgent."),owner:o.string().nullable().optional().describe("New owner. Set to null to unassign."),due_at:o.string().nullable().optional().describe("New due date as ISO 8601 datetime string, e.g. '2026-03-01T00:00:00.000Z'. Set to null to clear."),blocked_by:o.array(o.string()).optional().describe("Array of task IDs that block this task. Replaces the existing list."),labels:o.array(o.string()).optional().describe("Array of string labels. Replaces the existing list, e.g. ['bug', 'frontend']."),recurrence:o.string().nullable().optional().describe("Recurrence rule in RRULE format, e.g. 'FREQ=DAILY', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'. Prefix with 'after:' for completion-based (next due from completion time). Set to null to remove recurrence."),metadata:o.record(o.string(),o.unknown()).optional().describe("Arbitrary JSON object. Merged with existing metadata."),note:o.string().optional().describe("A note to append to the task. Append-only - existing notes are not affected.")})},async s=>{try{let{id:n,note:i,...a}=s;if(a.metadata!==void 0){let u=await r.get(n);a.metadata={...u?.metadata??{},...a.metadata}}let l=i?await r.updateWithNote(n,a,i):await r.update(n,a);return l?{content:[{type:"text",text:JSON.stringify(m(l),null,2)}]}:{content:[{type:"text",text:`Task not found: ${n}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:b(n)}],isError:!0}}}),e.registerTool("delete_task",{title:"Delete task",description:"Soft-delete a task by ID. The task is marked as deleted and excluded from listings but retained in the oplog for sync purposes.",inputSchema:o.object({id:o.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID")})},async({id:s})=>{try{return await r.delete(s)?{content:[{type:"text",text:`Deleted ${s}.`}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:b(n)}],isError:!0}}}),e.registerTool("list_tasks",{title:"List tasks",description:"List tasks with optional filters. Returns a JSON array of tasks. Done tasks are excluded by default - pass all: true to include them, or status='done' to see only completed tasks. Use 'actionable' filter to get only tasks that are not blocked and not done. The 'search' filter performs a case-insensitive substring match on task titles.",inputSchema:o.object({status:B.optional().describe("Filter by status. Valid values: todo, in_progress, in_review, done. Pass 'done' to see completed tasks."),priority:C.optional().describe("Filter by priority. Valid values: low, medium, high, urgent."),owner:o.string().optional().describe("Filter by owner, e.g. 'alice'"),label:o.string().optional().describe("Filter by label, e.g. 'bug'"),search:o.string().optional().describe("Substring search across task titles (case-insensitive), e.g. 'login'"),sort:o.enum(["priority","due","title","created"]).optional().describe("Sort order. Valid values: priority, due, title, created."),actionable:o.boolean().optional().describe("When true, returns only actionable tasks - those with status 'todo' that are not blocked by other incomplete tasks."),all:o.boolean().optional().describe("Include done tasks (ignored when status filter is set)"),limit:o.number().optional().describe("Maximum number of results to return"),offset:o.number().optional().describe("Number of results to skip (for pagination)")})},async s=>{try{let{all:n,...i}=s,a=await r.list(i);return a=G(a,{all:n,status:i.status}),{content:[{type:"text",text:JSON.stringify(a.map(m),null,2)}]}}catch(n){return{content:[{type:"text",text:b(n)}],isError:!0}}}),e.registerTool("get_task",{title:"Get task",description:"Get a single task by its full ID or a unique ID prefix. Supports prefix matching - e.g. passing '0196b8' will match if only one task ID starts with that prefix.",inputSchema:o.object({id:o.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID")})},async({id:s})=>{try{let n=await r.get(s);return n?{content:[{type:"text",text:JSON.stringify(m(n),null,2)}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:b(n)}],isError:!0}}}),e.registerTool("get_context",{title:"Get context",description:"Get a quick status overview of what needs attention. Returns counts and full task lists for: overdue, due soon (within 48h), in progress, actionable (todo + not blocked), blocked, and recently completed (last 24h). Use this for a high-level summary before deciding what to work on next.",inputSchema:o.object({owner:o.string().optional().describe("Scope to a specific owner, e.g. 'alice'"),label:o.string().optional().describe("Filter by label, e.g. 'backend'")})},async s=>{try{let{sections:n,summary:i}=await r.getContext({owner:s.owner,label:s.label}),a={summary:i};for(let[l,u]of Object.entries(n))l==="blockerTitles"?a[l]=u:a[l]=u.map(m);return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}catch(n){return{content:[{type:"text",text:b(n)}],isError:!0}}}),e.registerTool("add_note",{title:"Add note",description:"Append a note to an existing task. Notes are append-only and deduplicated - adding the same note text twice has no effect. Returns the updated task.",inputSchema:o.object({id:o.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID"),note:o.string().describe("Note text to append, e.g. 'Blocked on API review'")})},async({id:s,note:n})=>{try{let i=await r.addNote(s,n);return i?{content:[{type:"text",text:JSON.stringify(m(i),null,2)}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(i){return{content:[{type:"text",text:b(i)}],isError:!0}}}),e.registerTool("list_labels",{title:"List labels",description:"List all labels currently in use across all tasks. Returns a flat JSON array of label strings. Useful for discovering available labels before filtering with list_tasks.",inputSchema:o.object({})},async()=>{try{let s=await r.listLabels();return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(s){return{content:[{type:"text",text:b(s)}],isError:!0}}}),e}var Y=j();X(Y);var ve=q(Y),Oe=H(Y),Ne=new O(ve,Oe),Re=ne(Ne),Le=new Ie;await Re.connect(Le);
37
+ )`)),t?.sql&&(e=e.where(m`(${m.raw(t.sql)})`)),t?.sort??"priority"){case"due":e=e.orderBy(m`CASE WHEN due_at IS NULL THEN 1 ELSE 0 END`,"asc").orderBy("due_at","asc").orderBy("created_at","asc");break;case"title":e=e.orderBy(m`title COLLATE NOCASE`,"asc").orderBy("created_at","asc");break;case"created":e=e.orderBy("created_at","asc");break;default:e=e.orderBy(m`CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`).orderBy("created_at","asc");break}return(t?.limit!==void 0||t?.offset!==void 0)&&(e=e.limit(t.limit??-1)),t?.offset!==void 0&&(e=e.offset(t.offset)),(await e.execute()).map(P)}function ae(r,t){return t.all||t.status!==void 0?r:r.filter(e=>e.status!=="done")}async function p(r,t){let e=await r.selectFrom("tasks").selectAll().where("id","=",t).where("deleted_at","is",null).executeTakeFirst();if(e)return P(e);if(!t)return null;let s=t.replace(/[\\%_]/g,"\\$&"),n=await r.selectFrom("tasks").selectAll().where(m`id LIKE ${s} || '%' ESCAPE '\\'`).where("deleted_at","is",null).execute();if(n.length===1)return P(n[0]);if(n.length>1)throw new F(t,n.map(a=>a.id));return null}async function x(r,t,e,s){let n=await p(r,t);if(!n)return null;let i={updated_at:s??new Date().toISOString()};return e.title!==void 0&&(i.title=e.title),e.status!==void 0&&(i.status=e.status),e.priority!==void 0&&(i.priority=e.priority),e.owner!==void 0&&(i.owner=e.owner),e.due_at!==void 0&&(i.due_at=e.due_at),e.recurrence!==void 0&&(i.recurrence=e.recurrence),e.blocked_by!==void 0&&(i.blocked_by=JSON.stringify(e.blocked_by)),e.labels!==void 0&&(i.labels=JSON.stringify(e.labels)),e.metadata!==void 0&&(i.metadata=JSON.stringify(e.metadata)),await r.updateTable("tasks").set(i).where("id","=",n.id).execute(),p(r,n.id)}async function D(r,t,e,s){let n=await p(r,t);if(!n)return null;let a=e.trim();if(a.length===0||n.notes.some(c=>c.trim()===a))return n;let i=[...n.notes,a],o=s??new Date().toISOString();return await r.updateTable("tasks").set({notes:JSON.stringify(i),updated_at:o}).where("id","=",n.id).execute(),p(r,n.id)}async function v(r,t,e,s){let n=await p(r,t);if(!n)return null;let a=s??new Date().toISOString();return await r.updateTable("tasks").set({notes:JSON.stringify(e),updated_at:a}).where("id","=",n.id).execute(),p(r,n.id)}async function ie(r,t,e){let s=await p(r,t);if(!s)return!1;let n=e??new Date().toISOString(),a=await r.updateTable("tasks").set({deleted_at:n,updated_at:n}).where("id","=",s.id).where("deleted_at","is",null).executeTakeFirst();return BigInt(a.numUpdatedRows)>0n}async function f(r,t,e){let s=h(),n=e??new Date().toISOString();return await r.insertInto("oplog").values({id:s,task_id:t.task_id,device_id:t.device_id,op_type:t.op_type,field:t.field,value:t.value,timestamp:n}).execute(),{id:s,task_id:t.task_id,device_id:t.device_id,op_type:t.op_type,field:t.field,value:t.value,timestamp:n}}function $e(){return"NO_COLOR"in process.env?!1:"FORCE_COLOR"in process.env?!0:process.stdout.isTTY??!1}function I(r,t){let e=`\x1B[${r}m`,s=`\x1B[${t}m`;return n=>$e()?`${e}${n}${s}`:n}var Me=I(1,22),Ue=I(2,22),Fe=I(3,23),Pe=I(37,39);function Y(r,t){let e=t??new Date,s=new Date(Number(r.slice(0,4)),Number(r.slice(5,7))-1,Number(r.slice(8,10)),Number(r.slice(11,13))||0,Number(r.slice(14,16))||0);return r.slice(11,16)==="00:00"&&s.setDate(s.getDate()+1),s<e}function oe(r,t){if(Y(r,t))return!1;let e=t??new Date,s=new Date(Number(r.slice(0,4)),Number(r.slice(5,7))-1,Number(r.slice(8,10)),Number(r.slice(11,13))||0,Number(r.slice(14,16))||0);return r.slice(11,16)==="00:00"&&s.setDate(s.getDate()+1),(s.getTime()-e.getTime())/(1e3*60*60)<=48}var Be={SU:0,MO:1,TU:2,WE:3,TH:4,FR:5,SA:6};function Ye(r){let t=r.split(";"),e="",s=1,n=null,a=null;for(let i of t){let[o,c]=i.split("=");switch(o){case"FREQ":e=c;break;case"INTERVAL":s=Number(c);break;case"BYDAY":n=c.split(",");break;case"BYMONTHDAY":a=Number(c);break}}return{freq:e,interval:s,byDay:n,byMonthDay:a}}function O(r,t){let e=new Date(r);return e.setDate(e.getDate()+t),e}function C(r,t){let e=new Date(r),s=e.getMonth()+t;return e.setMonth(s),e.getMonth()!==(s%12+12)%12&&e.setDate(0),e}function le(r,t){let e=Ye(r);switch(e.freq){case"DAILY":return O(t,e.interval);case"WEEKLY":{if(e.byDay&&e.byDay.length>0){let s=e.byDay.map(o=>Be[o]).sort((o,c)=>o-c),n=t.getDay();for(let o of s)if(o>n)return O(t,o-n);let a=7-n+s[0],i=(e.interval-1)*7;return O(t,a+i)}return O(t,e.interval*7)}case"MONTHLY":{if(e.byMonthDay!==null){let s=e.byMonthDay,n=new Date(t);if(t.getDate()<s){if(n.setDate(s),n.getMonth()!==t.getMonth()&&(n=new Date(t.getFullYear(),t.getMonth()+1,0)),n.getTime()<=t.getTime()){n=C(t,e.interval);let a=n.getMonth();n.setDate(s),n.getMonth()!==a&&(n=new Date(n.getFullYear(),a+1,0))}}else{n=C(t,e.interval);let a=n.getMonth();n.setDate(s),n.getMonth()!==a&&(n=new Date(n.getFullYear(),a+1,0))}return n}return C(t,e.interval)}case"YEARLY":{let s=new Date(t);return s.setFullYear(s.getFullYear()+e.interval),s.getMonth()!==t.getMonth()&&s.setDate(0),s}default:throw new Error(`Unsupported FREQ: ${e.freq}`)}}import{createHash as Ce}from"crypto";var We="oru-recurrence";function W(r){let t=Ce("sha256").update(`${We}:${r}`).digest();return U(t.subarray(0,8),11)}function j(r){return r===null?null:typeof r=="string"?r:JSON.stringify(r)}function ce(r){return{title:r.title,status:r.status,priority:r.priority,owner:r.owner,due_at:r.due_at,recurrence:r.recurrence,blocked_by:r.blocked_by,labels:r.labels,notes:r.notes,metadata:r.metadata}}var N=class{constructor(t,e){this.db=t;this.deviceId=e}async add(t){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n={...t,owner:t.owner||null},a=await B(e,n,s);return await f(e,{task_id:a.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(ce(a))},s),a})}async _maybeSpawn(t,e,s){if(e.status!=="done"||!e.recurrence)return null;let n=W(e.id);if(await p(t,n))return null;let i=e.recurrence,o=i.startsWith("after:");o&&(i=i.slice(6));let c;o?c=new Date(s):e.due_at?c=new Date(e.due_at):c=new Date(s);let u=le(i,c),l=`${u.getFullYear()}-${String(u.getMonth()+1).padStart(2,"0")}-${String(u.getDate()).padStart(2,"0")}T${String(u.getHours()).padStart(2,"0")}:${String(u.getMinutes()).padStart(2,"0")}:${String(u.getSeconds()).padStart(2,"0")}`,k={id:n,title:e.title,priority:e.priority,owner:e.owner,due_at:l,recurrence:e.recurrence,labels:[...e.labels],metadata:{...e.metadata}},y=await B(t,k,s);return await f(t,{task_id:y.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(ce(y))},s),y}async getSpawnedTask(t){let e=W(t);return p(this.db,e)}async validateBlockedBy(t,e){let s=null;if(t!==null){let a=await p(this.db,t);if(!a)return{valid:!1,error:`Task "${t}" not found.`};s=a.id}let n=[];for(let a of e){let i=await p(this.db,a);if(!i)return{valid:!1,error:`Task "${a}" not found.`};if(s!==null&&i.id===s)return{valid:!1,error:"A task cannot block itself."};n.push(i.id)}if(s!==null&&n.length>0){let a=await E(this.db),i=new Map(a.map(o=>[o.id,o]));for(let o of n){let c=[o],u=new Set;for(;c.length>0;){let l=c.shift();if(l===s)return{valid:!1,error:`Setting blocked_by to "${o}" would create a circular dependency.`};if(u.has(l))continue;u.add(l);let k=i.get(l);if(k)for(let y of k.blocked_by)u.has(y)||c.push(y)}}}return{valid:!0}}async list(t){return E(this.db,t)}async get(t){return p(this.db,t)}async update(t,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),a=await x(s,t,e,n);if(!a)return null;for(let[i,o]of Object.entries(e))i==="note"||o===void 0||await f(s,{task_id:a.id,device_id:this.deviceId,op_type:"update",field:i,value:j(o)},n);return await this._maybeSpawn(s,a,n),a})}async addNote(t,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),a=await p(s,t);if(!a)return null;let i=e.trim();if(i.length===0||a.notes.some(c=>c.trim()===i))return a;let o=await D(s,a.id,i,n);return await f(s,{task_id:a.id,device_id:this.deviceId,op_type:"update",field:"notes",value:i},n),o})}async updateWithNote(t,e,s){return this.db.transaction().execute(async n=>{let a=new Date().toISOString(),i=await x(n,t,e,a);if(!i)return null;let o=i.id;for(let[u,l]of Object.entries(e))u==="note"||l===void 0||await f(n,{task_id:o,device_id:this.deviceId,op_type:"update",field:u,value:j(l)},a);let c=s.trim();return c.length>0&&!i.notes.some(u=>u.trim()===c)&&(i=await D(n,o,c,a),await f(n,{task_id:o,device_id:this.deviceId,op_type:"update",field:"notes",value:c},a)),await this._maybeSpawn(n,i,a),i})}async clearNotes(t){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n=await v(e,t,[],s);return n?(await f(e,{task_id:n.id,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},s),n):null})}async clearNotesAndUpdate(t,e,s){return this.db.transaction().execute(async n=>{let a=new Date().toISOString(),i=await v(n,t,[],a);if(!i)return null;let o=i.id;if(await f(n,{task_id:o,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},a),s){let u=s.trim();u.length>0&&(i=await D(n,o,u,a),await f(n,{task_id:o,device_id:this.deviceId,op_type:"update",field:"notes",value:u},a))}if(Object.keys(e).length>0){i=await x(n,o,e,a);for(let[u,l]of Object.entries(e))u==="note"||l===void 0||await f(n,{task_id:o,device_id:this.deviceId,op_type:"update",field:u,value:j(l)},a)}return await this._maybeSpawn(n,i,a),i})}async replaceNotes(t,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),a=await v(s,t,e,n);if(!a)return null;let i=a.id;await f(s,{task_id:i,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},n);for(let o of e)await f(s,{task_id:i,device_id:this.deviceId,op_type:"update",field:"notes",value:o},n);return a})}async listLabels(){let t=await E(this.db),e=new Set;for(let s of t)for(let n of s.labels)e.add(n);return[...e].sort()}async getContext(t){let e=new Date,s=await this.list({sort:"priority",owner:t?.owner,label:t?.label}),n=await this.list({status:"done",sort:"priority",owner:t?.owner,label:t?.label}),a={overdue:[],due_soon:[],in_progress:[],actionable:[],blocked:[],recently_completed:[]},i=new Set(s.filter(l=>l.status!=="done").map(l=>l.id)),o=new Date(e.getTime()-1440*60*1e3).toISOString();for(let l of n)l.updated_at>=o&&a.recently_completed.push(l);for(let l of s){if(l.status==="done")continue;if(l.status==="in_progress"||l.status==="in_review"){a.in_progress.push(l);continue}if(l.due_at&&Y(l.due_at,e)){a.overdue.push(l);continue}if(l.due_at&&oe(l.due_at,e)){a.due_soon.push(l);continue}if(l.blocked_by.some(y=>i.has(y))){a.blocked.push(l);continue}if(l.status==="todo"){a.actionable.push(l);continue}}let c=new Map;for(let l of[...s,...n])c.set(l.id,l.title);a.blockerTitles=c;let u={overdue:a.overdue.length,due_soon:a.due_soon.length,in_progress:a.in_progress.length,actionable:a.actionable.length,blocked:a.blocked.length,recently_completed:a.recently_completed.length};return{sections:a,summary:u}}async log(t){let e=await p(this.db,t);return e?await this.db.selectFrom("oplog").selectAll().where("task_id","=",e.id).orderBy("timestamp","asc").orderBy(je`rowid`,"asc").execute():null}async delete(t){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n=await p(e,t);if(!n)return!1;let a=await ie(e,n.id,s);return a&&await f(e,{task_id:n.id,device_id:this.deviceId,op_type:"delete",field:null,value:null},s),a})}};import{McpServer as Je}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as d}from"zod";function b(r){let{deleted_at:t,...e}=r;return e}import{z as g}from"zod";var Xe=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/,L=1e3,A=1e4,de=200,ue=100,pe=100,ge=100,fe=50,me=100,ye=5e3;var R=g.enum(_),$=g.enum(S),be=g.string().min(1,"Title is required.").max(L,`Title exceeds maximum length of ${L} characters.`),Te=g.string().max(L,`Title exceeds maximum length of ${L} characters.`),X=g.array(g.string().max(de,`Label exceeds maximum length of ${de} characters.`)).max(ue,`labels exceeds maximum of ${ue} items.`),he=g.array(g.string().max(A,`Note exceeds maximum length of ${A} characters.`)).max(ge,`notes exceeds maximum of ${ge} items.`),J=g.string().max(A,`Note exceeds maximum length of ${A} characters.`),K=g.array(g.string()).max(pe,`blocked_by exceeds maximum of ${pe} items.`),H=g.record(g.string(),g.unknown()).refine(r=>Object.keys(r).length<=fe,`Metadata exceeds maximum of ${fe} keys.`).refine(r=>Object.keys(r).every(t=>t.length<=me),`Metadata key exceeds maximum length of ${me} characters.`).refine(r=>Object.values(r).every(t=>typeof t!="string"||t.length<=ye),`Metadata value exceeds maximum length of ${ye} characters.`),Kt=g.string().regex(Xe,"Invalid date format. Expected YYYY-MM-DD, YYYY-MM-DDTHH:MM, or YYYY-MM-DDTHH:MM:SS.").nullable().optional();var ke="0.0.5";function Ke(r){return r instanceof Error&&"code"in r&&typeof r.code=="string"&&r.code.startsWith("SQLITE_")}function T(r){return Ke(r)||r instanceof TypeError?"An internal error occurred. Please try again.":r instanceof Error?r.message:"An internal error occurred. Please try again."}function _e(r){let t=ke,e=new Je({name:"oru",version:t},{capabilities:{logging:{}}});return e.registerTool("add_task",{title:"Add task",description:"Create a new task. Returns the created task. Defaults to status 'todo' and priority 'medium' if not specified. Pass an 'id' field to enable idempotent creates - if a task with that ID already exists, the existing task is returned instead of creating a duplicate.",inputSchema:d.object({title:be.describe("Task title, e.g. 'Fix login bug'"),id:d.string().optional().describe("Custom task ID for idempotent creates. If a task with this ID already exists, the existing task is returned. Must be a 11-character base62 string (alphabet: 0-9, A-Z, a-z)."),status:R.optional().describe("Initial status. Valid values: todo, in_progress, in_review, done. Defaults to 'todo'."),priority:$.optional().describe("Priority level. Valid values: low, medium, high, urgent. Defaults to 'medium'."),owner:d.string().optional().describe("Assign to owner, e.g. 'alice'"),due_at:d.string().optional().describe("Due date as ISO 8601 datetime string, e.g. '2026-03-01T00:00:00.000Z'"),blocked_by:K.optional().describe("Array of task IDs that must be completed before this task, e.g. ['0196b8e0-...']"),labels:X.optional().describe("Array of string labels to attach, e.g. ['bug', 'frontend']"),notes:he.optional().describe("Initial notes to add to the task, e.g. ['Started migration']"),recurrence:d.string().nullable().optional().describe("Recurrence rule in RRULE format, e.g. 'FREQ=DAILY', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'. Prefix with 'after:' for completion-based recurrence (next due computed from completion time instead of current due date), e.g. 'after:FREQ=WEEKLY'. Set to null to remove recurrence."),metadata:H.optional().describe("Arbitrary JSON object for storing custom key-value data, e.g. {pr: 42}")})},async s=>{try{let n=await r.add(s);return{content:[{type:"text",text:JSON.stringify(b(n),null,2)}]}}catch(n){let a=n instanceof Error?n.message:String(n);if(s.id&&a.includes("UNIQUE constraint")){let i=await r.get(s.id);if(i)return{content:[{type:"text",text:JSON.stringify(b(i),null,2)}]}}return{content:[{type:"text",text:T(n)}],isError:!0}}}),e.registerTool("update_task",{title:"Update task",description:"Update fields on an existing task. Only send the fields you want to change - omitted fields are left unchanged. Notes are append-only: use the 'note' field to add a new note without affecting existing ones. Returns the updated task.",inputSchema:d.object({id:d.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID"),title:Te.optional().describe("New title"),status:R.optional().describe("New status. Valid values: todo, in_progress, in_review, done."),priority:$.optional().describe("New priority. Valid values: low, medium, high, urgent."),owner:d.string().nullable().optional().describe("New owner. Set to null to unassign."),due_at:d.string().nullable().optional().describe("New due date as ISO 8601 datetime string, e.g. '2026-03-01T00:00:00.000Z'. Set to null to clear."),blocked_by:K.optional().describe("Array of task IDs that block this task. Replaces the existing list."),labels:X.optional().describe("Array of string labels. Replaces the existing list, e.g. ['bug', 'frontend']."),recurrence:d.string().nullable().optional().describe("Recurrence rule in RRULE format, e.g. 'FREQ=DAILY', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'. Prefix with 'after:' for completion-based (next due from completion time). Set to null to remove recurrence."),metadata:H.optional().describe("Arbitrary JSON object. Merged with existing metadata."),note:J.optional().describe("A note to append to the task. Append-only - existing notes are not affected.")})},async s=>{try{let{id:n,note:a,...i}=s;if(i.metadata!==void 0){let c=await r.get(n);i.metadata={...c?.metadata??{},...i.metadata}}let o=a?await r.updateWithNote(n,i,a):await r.update(n,i);return o?{content:[{type:"text",text:JSON.stringify(b(o),null,2)}]}:{content:[{type:"text",text:`Task not found: ${n}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:T(n)}],isError:!0}}}),e.registerTool("delete_task",{title:"Delete task",description:"Soft-delete a task by ID. The task is marked as deleted and excluded from listings but retained in the oplog for sync purposes.",inputSchema:d.object({id:d.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID")})},async({id:s})=>{try{return await r.delete(s)?{content:[{type:"text",text:`Deleted ${s}.`}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:T(n)}],isError:!0}}}),e.registerTool("list_tasks",{title:"List tasks",description:"List tasks with optional filters. Returns a JSON array of tasks. Done tasks are excluded by default - pass all: true to include them, or status='done' to see only completed tasks. Use 'actionable' filter to get only tasks that are not blocked and not done. The 'search' filter performs a case-insensitive substring match on task titles.",inputSchema:d.object({status:R.optional().describe("Filter by status. Valid values: todo, in_progress, in_review, done. Pass 'done' to see completed tasks."),priority:$.optional().describe("Filter by priority. Valid values: low, medium, high, urgent."),owner:d.string().optional().describe("Filter by owner, e.g. 'alice'"),label:d.string().optional().describe("Filter by label, e.g. 'bug'"),search:d.string().optional().describe("Substring search across task titles (case-insensitive), e.g. 'login'"),sort:d.enum(["priority","due","title","created"]).optional().describe("Sort order. Valid values: priority, due, title, created."),actionable:d.boolean().optional().describe("When true, returns only actionable tasks - those with status 'todo' that are not blocked by other incomplete tasks."),all:d.boolean().optional().describe("Include done tasks (ignored when status filter is set)"),limit:d.number().optional().describe("Maximum number of results to return"),offset:d.number().optional().describe("Number of results to skip (for pagination)")})},async s=>{try{let{all:n,...a}=s,i=await r.list(a);return i=ae(i,{all:n,status:a.status}),{content:[{type:"text",text:JSON.stringify(i.map(b),null,2)}]}}catch(n){return{content:[{type:"text",text:T(n)}],isError:!0}}}),e.registerTool("get_task",{title:"Get task",description:"Get a single task by its full ID or a unique ID prefix. Supports prefix matching - e.g. passing '0196b8' will match if only one task ID starts with that prefix.",inputSchema:d.object({id:d.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID")})},async({id:s})=>{try{let n=await r.get(s);return n?{content:[{type:"text",text:JSON.stringify(b(n),null,2)}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:T(n)}],isError:!0}}}),e.registerTool("get_context",{title:"Get context",description:"Get a quick status overview of what needs attention. Returns counts and full task lists for: overdue, due soon (within 48h), in progress, actionable (todo + not blocked), blocked, and recently completed (last 24h). Use this for a high-level summary before deciding what to work on next.",inputSchema:d.object({owner:d.string().optional().describe("Scope to a specific owner, e.g. 'alice'"),label:d.string().optional().describe("Filter by label, e.g. 'backend'")})},async s=>{try{let{sections:n,summary:a}=await r.getContext({owner:s.owner,label:s.label}),i={summary:a};for(let[o,c]of Object.entries(n))o==="blockerTitles"?i[o]=c:i[o]=c.map(b);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(n){return{content:[{type:"text",text:T(n)}],isError:!0}}}),e.registerTool("add_note",{title:"Add note",description:"Append a note to an existing task. Notes are append-only and deduplicated - adding the same note text twice has no effect. Returns the updated task.",inputSchema:d.object({id:d.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID"),note:J.describe("Note text to append, e.g. 'Blocked on API review'")})},async({id:s,note:n})=>{try{let a=await r.addNote(s,n);return a?{content:[{type:"text",text:JSON.stringify(b(a),null,2)}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(a){return{content:[{type:"text",text:T(a)}],isError:!0}}}),e.registerTool("list_labels",{title:"List labels",description:"List all labels currently in use across all tasks. Returns a flat JSON array of label strings. Useful for discovering available labels before filtering with list_tasks.",inputSchema:d.object({})},async()=>{try{let s=await r.listLabels();return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(s){return{content:[{type:"text",text:T(s)}],isError:!0}}}),e}var q=G();z(q);var qe=Z(q),Ve=te(q),Ge=new N(qe,Ve),Qe=_e(Ge),ze=new He;await Qe.connect(ze);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tchayen/oru",
3
- "version": "0.0.1",
3
+ "version": "0.0.5",
4
4
  "description": "oru - agent-friendly todo CLI with offline sync",
5
5
  "keywords": [
6
6
  "agent",
@@ -31,14 +31,6 @@
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
- "scripts": {
35
- "test": "vitest run",
36
- "test:watch": "vitest",
37
- "prepack": "cp ../README.md ../LICENSE . 2>/dev/null || true",
38
- "build": "tsup",
39
- "dev": "tsx src/cli.ts",
40
- "tsgo": "tsgo --noEmit"
41
- },
42
34
  "dependencies": {
43
35
  "@hono/node-server": "^1.19.9",
44
36
  "@modelcontextprotocol/sdk": "^1.26.0",
@@ -52,7 +44,6 @@
52
44
  "zod": "^4.3.6"
53
45
  },
54
46
  "devDependencies": {
55
- "@oru/types": "workspace:*",
56
47
  "@types/better-sqlite3": "^7.6.13",
57
48
  "@types/node": "^22.13.0",
58
49
  "@types/qrcode": "^1.5.6",
@@ -64,5 +55,12 @@
64
55
  },
65
56
  "engines": {
66
57
  "node": ">=22"
58
+ },
59
+ "scripts": {
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "build": "tsup",
63
+ "dev": "tsx src/cli.ts",
64
+ "tsgo": "tsgo --noEmit"
67
65
  }
68
- }
66
+ }