@tchayen/oru 0.0.5 → 0.0.6

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 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()}
2
+ var nr=Object.defineProperty;var q=(t,e)=>()=>(t&&(e=t(t=0)),e);var Me=(t,e)=>{for(var n in e)nr(t,n,{get:e[n],enumerable:!0})};import ce from"fs";import kt from"path";import Ar from"os";import{parse as Lr}from"smol-toml";function he(){return process.env.ORU_CONFIG_DIR?kt.join(process.env.ORU_CONFIG_DIR,"config.toml"):kt.join(Ar.homedir(),".oru","config.toml")}function vt(t){let e=t??he();if(!ce.existsSync(e))return{...St};let n=ce.readFileSync(e,"utf-8"),o;try{o=Lr(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
+ `),{...St}}let r={...St};return typeof o.date_format=="string"&&Mr.has(o.date_format)&&(r.date_format=o.date_format),typeof o.first_day_of_week=="string"&&Cr.has(o.first_day_of_week.toLowerCase())&&(r.first_day_of_week=o.first_day_of_week.toLowerCase()),typeof o.output_format=="string"&&Ur.has(o.output_format)&&(r.output_format=o.output_format),typeof o.next_month=="string"&&Pr.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 Te(t,e){let n=he(),o="";ce.existsSync(n)&&(o=ce.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
- `,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
5
+ `,ce.mkdirSync(kt.dirname(n),{recursive:!0}),ce.writeFileSync(n,o)}var St,Mr,Cr,Ur,Pr,En,wt=q(()=>{"use strict";St={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},Mr=new Set(["dmy","mdy"]),Cr=new Set(["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]),Ur=new Set(["text","json"]),Pr=new Set(["same_day","first"]);En=`# 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 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(`
41
+ `});var pe=q(()=>{"use strict"});var z,Bn,Ne=q(()=>{"use strict";z="0.0.6",Bn="ae0395e"});var Ct={};Me(Ct,{buildEvent:()=>yo,detectCI:()=>Wn,extractCommandAndFlags:()=>po,getTelemetryDisabledReason:()=>Mt,isTelemetryEnabled:()=>Lt,sendEvent:()=>go,showFirstRunNotice:()=>_o});function Lt(t){return!(process.env.DO_NOT_TRACK==="1"||process.env.ORU_TELEMETRY_DISABLED==="1"||t.telemetry===!1)}function Mt(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 mo.some(t=>process.env[t])}function po(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 c=a.includes("=")?a.slice(0,a.indexOf("=")):a;n.push(c),!a.includes("=")&&s+1<e.length&&!e[s+1].startsWith("-")&&s++}else r?r&&!o.includes(" ")&&fo(o,a)&&(o=`${o} ${a}`):(o=a,r=!0)}return{command:o||"(unknown)",flags:n}}function fo(t,e){return{config:["init","path"],filter:["add","list","show","remove"],completions:["bash","zsh","fish"],telemetry:["status","enable","disable"],...{}}[t]?.includes(e)??!1}function go(t){let e=process.env.ORU_TELEMETRY_URL??co;try{let n=new AbortController,o=setTimeout(()=>n.abort(),uo);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 _o(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
- `),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}
45
+ `),Te("telemetry_notice_shown","true")}catch(e){process.env.ORU_DEBUG==="1"&&console.error("Telemetry notice failed:",e)}}function yo(t,e,n,o,r){let s={cli_version:z,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 co,uo,mo,nt=q(()=>{"use strict";wt();pe();Ne();co="https://telemetry.oru.sh/v1/events",uo=3e3;mo=["CI","GITHUB_ACTIONS","GITLAB_CI","CIRCLECI","TRAVIS","JENKINS_URL","BUILDKITE","TF_BUILD"]});var Hn={};Me(Hn,{autoBackup:()=>ho,performBackup:()=>rt,shouldAutoBackup:()=>zn});import Ie from"fs";import Re from"path";import Jn from"os";function bo(){return`oru-${new Date().toISOString().replace(/[:.]/g,"-").replace("Z","")}.db`}function rt(t,e){let n=e.startsWith("~")?Re.join(Jn.homedir(),e.slice(1)):e;Ie.mkdirSync(n,{recursive:!0});let o=bo(),r=o.slice(0,-3),s=Re.join(n,o),a=1;for(;Ie.existsSync(s);)s=Re.join(n,`${r}-${a}.db`),a++;return t.exec(`VACUUM INTO '${s.replace(/'/g,"''")}'`),s}function zn(t,e){let n=t.startsWith("~")?Re.join(Jn.homedir(),t.slice(1)):t;if(!Ie.existsSync(n))return!0;let o=Ie.readdirSync(n).filter(a=>a.startsWith("oru-")&&a.endsWith(".db")).sort();if(o.length===0)return!0;let r=Ie.statSync(Re.join(n,o[o.length-1]));return(Date.now()-r.mtimeMs)/1e3/60>=e}function ho(t,e,n){try{zn(e,n)&&rt(t,e)}catch(o){process.env.ORU_DEBUG==="1"&&console.error("Auto-backup failed:",o)}}var Ut=q(()=>{"use strict"});async function ot(){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 Pt=q(()=>{"use strict"});var Yt={};Me(Yt,{checkForUpdate:()=>wo,compareVersions:()=>Ae,printUpdateNotice:()=>Eo});import Ft from"fs";import jt from"path";import To from"os";function Vn(){let t=process.env.ORU_INSTALL_DIR??jt.join(To.homedir(),".oru");return jt.join(t,".update-state.json")}function ko(){try{let t=Ft.readFileSync(Vn(),"utf-8");return JSON.parse(t)}catch{return null}}function vo(t){let e=Vn();Ft.mkdirSync(jt.dirname(e),{recursive:!0}),Ft.writeFileSync(e,JSON.stringify(t))}function Ae(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 wo(t){if(!t.auto_update_check||process.env.ORU_NO_UPDATE_CHECK==="1"||!process.stderr.isTTY)return null;let e=ko(),n=Date.now();if(e&&n-e.lastChecked<So)return Ae(e.latestVersion,z)>0?e.latestVersion:null;let o=await ot();return o?(vo({lastChecked:n,latestVersion:o}),Ae(o,z)>0?o:null):null}function Eo(t){process.stderr.write(`
46
+ Update available: ${z} \u2192 ${t}
47
47
  Run \`oru self-update\` to upgrade.
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
48
+ `)}var So,st=q(()=>{"use strict";Ne();Pt();So=1440*60*1e3});var Kn={};Me(Kn,{performUpdate:()=>Io});import{execSync as Xn}from"child_process";import Q from"fs";import ge from"path";import Bt from"os";function qn(){let t=process.env.ORU_INSTALL_DIR??ge.join(Bt.homedir(),".oru");return ge.join(t,".install-meta")}function xo(){try{let t=Q.readFileSync(qn(),"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 Oo(){return xo()?.install_method==="script"?"script":"npm"}function Do(){let t=process.platform==="darwin"?"darwin":"linux",e=process.arch==="arm64"?"arm64":"x64";return`${t}-${e}`}async function $o(){process.stderr.write(`Updating via npm...
50
+ `),Xn("npm install -g @tchayen/oru@latest",{stdio:"inherit"})}async function No(t){let e=process.env.ORU_INSTALL_DIR??ge.join(Bt.homedir(),".oru"),n=ge.join(e,"bin"),o=Do(),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),c=await fetch(r,{signal:s.signal});if(clearTimeout(a),!c.ok)throw new Error(`Failed to download: ${c.status} from ${r}`);let p=Q.mkdtempSync(ge.join(Bt.tmpdir(),"oru-update-")),m=ge.join(p,"oru.tar.gz"),d=Buffer.from(await c.arrayBuffer());Q.writeFileSync(m,d),Q.existsSync(n)&&Q.rmSync(n,{recursive:!0}),Q.mkdirSync(n,{recursive:!0}),Xn(`tar -xzf "${m}" -C "${n}"`,{stdio:"pipe"}),Q.rmSync(p,{recursive:!0});let g=`install_method=script
52
52
  version=${t}
53
53
  platform=${o}
54
54
  installed_at=${new Date().toISOString()}
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})
55
+ `;Q.writeFileSync(qn(),g),process.stderr.write(`Updated to oru v${t}
56
+ `)}async function Io(t){let e=await ot();if(!e)throw new Error("Failed to fetch latest version from npm registry.");let n=z;if(Ae(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;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 (
58
+ `),t)return;Oo()==="script"?await No(e):await $o()}var Gn=q(()=>{"use strict";st();Pt();Ne()});import{fileURLToPath as Jt}from"url";import Wt from"fs";import Le from"path";import{spawn as Qn}from"child_process";import{Command as Ro,Option as Z,Help as Ao}from"commander";import{sql as _r}from"kysely";import{sql as H}from"kysely";import{randomBytes as rr}from"crypto";var Vt="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",or=new Set(Vt),Xt=11;function lt(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(Vt[Number(n%62n)]),n/=62n;return o.reverse().join("")}function qt(t){if(t.length!==Xt)return!1;for(let e of t)if(!or.has(e))return!1;return!0}function re(){return lt(rr(8),Xt)}var I=["todo","in_progress","in_review","done"],oe=new Set(I),R=["low","medium","high","urgent"],se=new Set(R),Kt=["title","status","priority","owner","due_at","due_tz","recurrence","blocked_by","labels","metadata"],ct=new Set(Kt);var Gt="todo",Qt="medium";var W=["priority","due","title","created"],ee=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 Ce(t,e){try{return JSON.parse(t)}catch{return e}}function ut(t){return{id:t.id,title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,due_tz:t.due_tz,recurrence:t.recurrence,blocked_by:Ce(t.blocked_by,[]),labels:Ce(t.labels,[]),notes:Ce(t.notes,[]),metadata:Ce(t.metadata,{}),created_at:t.created_at,updated_at:t.updated_at,deleted_at:t.deleted_at}}async function dt(t,e,n){let o=e.id??re(),r=n??new Date().toISOString(),s={id:o,title:e.title,status:e.status??Gt,priority:e.priority??Qt,owner:e.owner??null,due_at:e.due_at??null,due_tz:e.due_tz??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,due_tz:s.due_tz,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 Ue(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 (
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(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(`
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(ut)}function Zt(t,e){return e.all||e.status!==void 0?t:t.filter(n=>n.status!=="done")}async function A(t,e){let n=await t.selectFrom("tasks").selectAll().where("id","=",e).where("deleted_at","is",null).executeTakeFirst();if(n)return ut(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 ut(r[0]);if(r.length>1)throw new ee(e,r.map(s=>s.id));return null}async function Pe(t,e,n,o){let r=await A(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.due_tz!==void 0&&(a.due_tz=n.due_tz),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(),A(t,r.id)}async function Fe(t,e,n,o){let r=await A(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],c=o??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(a),updated_at:c}).where("id","=",r.id).execute(),A(t,r.id)}async function je(t,e,n,o){let r=await A(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(),A(t,r.id)}async function en(t,e,n){let o=await A(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 U(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 sr(){return"NO_COLOR"in process.env?!1:"FORCE_COLOR"in process.env?!0:process.stdout.isTTY??!1}function Ye(t,e){let n=`\x1B[${t}m`,o=`\x1B[${e}m`;return r=>sr()?`${n}${r}${o}`:r}var L=Ye(1,22),v=Ye(2,22),mt=Ye(3,23),_e=Ye(37,39);var ar={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,c=null;for(let d of o){let[g,y]=d.split("=");switch(g){case"FREQ":r=y;break;case"INTERVAL":s=Number(y);break;case"BYDAY":a=y.split(",");break;case"BYMONTHDAY":c=Number(y);break}}if(a&&a.length>0){let d=["MO","TU","WE","TH","FR"];if(a.length===5&&d.every(x=>a.includes(x)))return`weekdays${n}`;let y=a.map(x=>ar[x]??x.toLowerCase());return`${s>1?`every ${s} weeks on `:"every "}${y.join(", ")}${n}`}if(c!==null){let d=ir(c);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 ir(t){let e=["th","st","nd","rd"],n=t%100;return t+(e[(n-20)%10]||e[n]||e[0])}var ye={sunday:0,monday:1,tuesday:2,wednesday:3,thursday:4,friday:5,saturday:6};function tn(t,e){let o=new Intl.DateTimeFormat("en-US",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(new Date(t)),r=y=>Number(o.find(E=>E.type===y).value),s=r("year"),a=r("month")-1,c=r("day"),p=r("hour")===24?0:r("hour"),m=r("minute"),d=r("second");return(Date.UTC(s,a,c,p,m,d)-t)/6e4}function ie(t,e){let n=Number(t.slice(0,4)),o=Number(t.slice(5,7))-1,r=Number(t.slice(8,10)),s=Number(t.slice(11,13))||0,a=Number(t.slice(14,16))||0,c=Number(t.slice(17,19))||0,p=Date.UTC(n,o,r,s,a,c),m=tn(p,e),d=p-m*6e4,g=tn(d,e);return p-g*6e4}function Be(t,e){return new Intl.DateTimeFormat("en-US",{timeZone:e,timeZoneName:"short"}).formatToParts(new Date(t)).find(r=>r.type==="timeZoneName")?.value??e}function We(t){let e=Number(t.slice(0,4)),n=Number(t.slice(5,7))-1,o=Number(t.slice(8,10)),r=Number(t.slice(11,13))||0,s=Number(t.slice(14,16))||0,a=Number(t.slice(17,19))||0;return new Date(Date.UTC(e,n,o,r,s,a))}function nn(t){let e=t.getUTCFullYear(),n=String(t.getUTCMonth()+1).padStart(2,"0"),o=String(t.getUTCDate()).padStart(2,"0"),r=String(t.getUTCHours()).padStart(2,"0"),s=String(t.getUTCMinutes()).padStart(2,"0"),a=String(t.getUTCSeconds()).padStart(2,"0");return`${e}-${n}-${o}T${r}:${s}:${a}`}function rn(t,e){let n=e??new Date;return new Intl.DateTimeFormat("en-CA",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit"}).format(n)}function le(t,e,n){let o=e??new Date;if(n){let s=ie(t,n);return(t.slice(11,16)==="00:00"||!t.includes("T"))&&(s+=1440*60*1e3),s<o.getTime()}let r=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"&&r.setDate(r.getDate()+1),r<o}function on(t,e,n){if(le(t,e,n))return!1;let o=e??new Date;if(n){let a=ie(t,n);return(t.slice(11,16)==="00:00"||!t.includes("T"))&&(a+=1440*60*1e3),(a-o.getTime())/(1e3*60*60)<=48}let r=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"&&r.setDate(r.getDate()+1),(r.getTime()-o.getTime())/(1e3*60*60)<=48}function lr(t,e,n){let o=t.slice(0,10),r=t.slice(11,16),s=r==="00:00"?o:`${o} ${r}`;if(n){let a=ie(t,n),c=Be(a,n);s+=` ${c}`}return le(t,e,n)?L(s):s}function cr(t,e){let n=t.slice(0,10),o=t.slice(11,16),r=o==="00:00"?n:`${n} ${o}`;if(e){let s=ie(t,e),a=Be(s,e);r+=` ${a}`}return r}function sn(t){switch(t){case"urgent":return L(t);case"low":return v(t);default:return t}}function ur(t){switch(t){case"done":return v(t);case"in_progress":return L(t);case"in_review":return mt(t);default:return t}}function dr(t){switch(t){case"done":return v("[x]");case"in_progress":return L("[~]");case"in_review":return _e("[r]");default:return v("[ ]")}}function J(t,e){let n=[];n.push(`${v(t.id)} ${L(t.title)}`);let o=` Status: ${ur(t.status)} Priority: ${sn(t.priority)}`;if(t.due_at&&(o+=` Due: ${lr(t.due_at,e,t.due_tz)}`),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(` ${v("Notes:")}`);for(let s of t.notes)n.push(` ${v("-")} ${mt(s)}`)}let r=Object.keys(t.metadata);if(r.length>0){n.push(` ${v("Metadata:")}`);for(let s of r)n.push(` ${v(`${s}:`)} ${String(t.metadata[s])}`)}return n.join(`
63
+ `)}function an(t){return t.length===0?v("No labels found."):t.join(`
64
+ `)}function pt(t,e){if(t.length===0)return`${v("No tasks found.")}
65
+ ${v('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=>{if(!d.due_at)return 0;let y=d.due_at.slice(11,16)==="00:00"?10:16;if(d.due_tz){let E=ie(d.due_at,d.due_tz),x=Be(E,d.due_tz);y+=1+x.length}return y})),a=Math.max(6,...t.map(d=>(d.labels.length>0?d.labels.join(", "):"").length)),c=Math.max(5,...t.map(d=>d.title.length)),p=v(` ${"ID".padEnd(n)} ${"TITLE".padEnd(c)} ${"PRI".padEnd(o)} ${"OWNER".padEnd(r)} ${"DUE".padEnd(s)} ${"LABELS".padEnd(a)} META`),m=t.map(d=>{let g=dr(d.status),y=d.owner??"",E=d.due_at?cr(d.due_at,d.due_tz):"",x=d.due_at?le(d.due_at,e,d.due_tz):!1,D=d.labels.length>0?d.labels.join(", "):"",T=Object.keys(d.metadata),$=T.length>0?T.map(_=>`${_}=${d.metadata[_]}`).join(", "):"",O=E.padEnd(s),j=x?L(O):O;return`${g} ${v(d.id.padEnd(n))} ${L(d.title.padEnd(c))} ${sn(d.priority.padEnd(o))} ${y.padEnd(r)} ${j} ${D.padEnd(a)} ${$}`});return[p,...m].join(`
66
+ `)}function ln(t){if(t.length===0)return v("No log entries found.");let e=[];for(let n of t){let o=v(n.timestamp),r=v(`(${n.device_id})`),s;switch(n.op_type){case"create":s=L("CREATE");break;case"delete":s=v("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),c=[];for(let[p,m]of Object.entries(a))m!=null&&c.push(`${p} = ${JSON.stringify(m)}`);c.length>0&&e.push(` ${c.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 cn(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 v("Nothing to report.");let s=r.map(([p,m])=>`${L(String(m.length))} ${o[p]}`),c=[v(s.join(", "))];for(let[p,m]of r)if(c.push(`${L(p)} ${v(`(${m.length})`)}`),c.push(pt(m,e)),p==="Blocked"&&t.blockerTitles){for(let d of m)if(d.blocked_by.length>0){let g=d.blocked_by.map(y=>{let E=t.blockerTitles.get(y);return E?`${y} (${E})`:y}).join(", ");c.push(v(` ${d.id} blocked by: ${g}`))}}return c.join(`
68
+
69
+ `)}function ft(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=>{if(!a.due_at)return!1;if(a.due_tz){let c=rn(a.due_tz,r);return a.due_at.slice(0,10)===c}return a.due_at.slice(0,10)===s});case"this-week":{let a=r.getDay(),c=ye[o],p=(a-c+7)%7,m=new Date(r.getFullYear(),r.getMonth(),r.getDate()-p),d=new Date(m.getFullYear(),m.getMonth(),m.getDate()+6),g=`${m.getFullYear()}-${String(m.getMonth()+1).padStart(2,"0")}-${String(m.getDate()).padStart(2,"0")}`,y=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;return t.filter(E=>{if(!E.due_at)return!1;let x=E.due_at.slice(0,10);return x>=g&&x<=y})}case"overdue":return t.filter(a=>a.due_at?le(a.due_at,r,a.due_tz):!1)}}var mr={SU:0,MO:1,TU:2,WE:3,TH:4,FR:5,SA:6};function pr(t){let e=t.split(";"),n="",o=1,r=null,s=null;for(let a of e){let[c,p]=a.split("=");switch(c){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 Je(t,e){let n=new Date(t);return n.setUTCDate(n.getUTCDate()+e),n}function gt(t,e){let n=new Date(t),o=n.getUTCMonth()+e;return n.setUTCMonth(o),n.getUTCMonth()!==(o%12+12)%12&&n.setUTCDate(0),n}function _t(t,e){let n=pr(t);switch(n.freq){case"DAILY":return Je(e,n.interval);case"WEEKLY":{if(n.byDay&&n.byDay.length>0){let o=n.byDay.map(c=>mr[c]).sort((c,p)=>c-p),r=e.getUTCDay();for(let c of o)if(c>r)return Je(e,c-r);let s=7-r+o[0],a=(n.interval-1)*7;return Je(e,s+a)}return Je(e,n.interval*7)}case"MONTHLY":{if(n.byMonthDay!==null){let o=n.byMonthDay,r=new Date(e);if(e.getUTCDate()<o){if(r.setUTCDate(o),r.getUTCMonth()!==e.getUTCMonth()&&(r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth()+1,0))),r.getTime()<=e.getTime()){r=gt(e,n.interval);let s=r.getUTCMonth();r.setUTCDate(o),r.getUTCMonth()!==s&&(r=new Date(Date.UTC(r.getUTCFullYear(),s+1,0)))}}else{r=gt(e,n.interval);let s=r.getUTCMonth();r.setUTCDate(o),r.getUTCMonth()!==s&&(r=new Date(Date.UTC(r.getUTCFullYear(),s+1,0)))}return r}return gt(e,n.interval)}case"YEARLY":{let o=new Date(e);return o.setUTCFullYear(o.getUTCFullYear()+n.interval),o.getUTCMonth()!==e.getUTCMonth()&&o.setUTCDate(0),o}default:throw new Error(`Unsupported FREQ: ${n.freq}`)}}import{createHash as fr}from"crypto";var gr="oru-recurrence";function ze(t){let e=fr("sha256").update(`${gr}:${t}`).digest();return lt(e.subarray(0,8),11)}function yt(t){return t===null?null:typeof t=="string"?t:JSON.stringify(t)}function un(t){return{title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,due_tz:t.due_tz,recurrence:t.recurrence,blocked_by:t.blocked_by,labels:t.labels,notes:t.notes,metadata:t.metadata}}var He=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 dt(n,r,o);return await U(n,{task_id:s.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(un(s))},o),s})}async _maybeSpawn(e,n,o){if(n.status!=="done"||!n.recurrence)return null;let r=ze(n.id);if(await A(e,r))return null;let a=n.recurrence,c=a.startsWith("after:");c&&(a=a.slice(6));let p;if(c)if(n.due_tz){let x=new Intl.DateTimeFormat("en-US",{timeZone:n.due_tz,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(new Date(o)),D=$=>Number(x.find(O=>O.type===$).value),T=D("hour")===24?0:D("hour");p=new Date(Date.UTC(D("year"),D("month")-1,D("day"),T,D("minute"),D("second")))}else p=We(o.slice(0,19));else n.due_at?p=We(n.due_at):p=We(o.slice(0,19));let m=_t(a,p),d=nn(m),g={id:r,title:n.title,priority:n.priority,owner:n.owner,due_at:d,due_tz:n.due_tz,recurrence:n.recurrence,labels:[...n.labels],metadata:{...n.metadata}},y=await dt(e,g,o);return await U(e,{task_id:y.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(un(y))},o),y}async getSpawnedTask(e){let n=ze(e);return A(this.db,n)}async validateBlockedBy(e,n){let o=null;if(e!==null){let s=await A(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 A(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 Ue(this.db),a=new Map(s.map(c=>[c.id,c]));for(let c of r){let p=[c],m=new Set;for(;p.length>0;){let d=p.shift();if(d===o)return{valid:!1,error:`Setting blocked_by to "${c}" would create a circular dependency.`};if(m.has(d))continue;m.add(d);let g=a.get(d);if(g)for(let y of g.blocked_by)m.has(y)||p.push(y)}}}return{valid:!0}}async list(e){return Ue(this.db,e)}async get(e){return A(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,c]of Object.entries(n))a==="note"||c===void 0||await U(o,{task_id:s.id,device_id:this.deviceId,op_type:"update",field:a,value:yt(c)},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 A(o,e);if(!s)return null;let a=n.trim();if(a.length===0||s.notes.some(p=>p.trim()===a))return s;let c=await Fe(o,s.id,a,r);return await U(o,{task_id:s.id,device_id:this.deviceId,op_type:"update",field:"notes",value:a},r),c})}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 c=a.id;for(let[m,d]of Object.entries(n))m==="note"||d===void 0||await U(r,{task_id:c,device_id:this.deviceId,op_type:"update",field:m,value:yt(d)},s);let p=o.trim();return p.length>0&&!a.notes.some(m=>m.trim()===p)&&(a=await Fe(r,c,p,s),await U(r,{task_id:c,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 je(n,e,[],o);return r?(await U(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 je(r,e,[],s);if(!a)return null;let c=a.id;if(await U(r,{task_id:c,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},s),o){let m=o.trim();m.length>0&&(a=await Fe(r,c,m,s),await U(r,{task_id:c,device_id:this.deviceId,op_type:"update",field:"notes",value:m},s))}if(Object.keys(n).length>0){a=await Pe(r,c,n,s);for(let[m,d]of Object.entries(n))m==="note"||d===void 0||await U(r,{task_id:c,device_id:this.deviceId,op_type:"update",field:m,value:yt(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 je(o,e,n,r);if(!s)return null;let a=s.id;await U(o,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},r);for(let c of n)await U(o,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes",value:c},r);return s})}async listLabels(){let e=await Ue(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)),c=new Date(n.getTime()-1440*60*1e3).toISOString();for(let d of r)d.updated_at>=c&&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&&le(d.due_at,n,d.due_tz)){s.overdue.push(d);continue}if(d.due_at&&on(d.due_at,n,d.due_tz)){s.due_soon.push(d);continue}if(d.blocked_by.some(y=>a.has(y))){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 A(this.db,e);return n?await this.db.selectFrom("oplog").selectAll().where("task_id","=",n.id).orderBy("timestamp","asc").orderBy(_r`rowid`,"asc").execute():null}async delete(e){return this.db.transaction().execute(async n=>{let o=new Date().toISOString(),r=await A(n,e);if(!r)return!1;let s=await en(n,r.id,o);return s&&await U(n,{task_id:r.id,device_id:this.deviceId,op_type:"delete",field:null,value:null},o),s})}};import{Kysely as yr,SqliteDialect as br}from"kysely";function dn(t){return new yr({dialect:new br({database:t})})}import hr from"better-sqlite3";import mn from"path";import Tr from"os";import bt from"fs";function Sr(){return process.env.ORU_DB_PATH?process.env.ORU_DB_PATH:mn.join(Tr.homedir(),".oru","oru.db")}function pn(t){let e=t??Sr(),n=mn.dirname(e);bt.existsSync(n)||bt.mkdirSync(n,{recursive:!0,mode:448});let o=new hr(e);o.pragma("journal_mode = WAL"),o.pragma("foreign_keys = ON");try{bt.chmodSync(e,384)}catch{}return o}function kr(t){let e=t.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();return e?parseInt(e.value,10):0}function fn(t,e){let n=kr(t),o=e.filter(a=>a.version>n).sort((a,c)=>a.version-c.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 gn(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
- `),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(`
100
+ `),fn(t,vr)}var vr=[{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")}},{version:8,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN due_tz TEXT")}}];function _n(t){let{deleted_at:e,...n}=t;return n}function yn(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(_n));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 bn(t){return JSON.stringify(t.map(_n),null,2)}function hn(t){return JSON.stringify(t,null,2)}function Tn(t){return JSON.stringify(t,null,2)}import wr from"better-sqlite3";import Sn from"fs";import Er from"path";var Ve=class{db;constructor(e){Sn.mkdirSync(Er.dirname(e),{recursive:!0}),this.db=new wr(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,35 +108,36 @@ ${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{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
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
111
+ `);try{Sn.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:c,...p})=>p),cursor:String(s)}}close(){this.db.close()}};import Tt from"fs";import Ir from"path";import Rr from"os";var xr=new Set(["create","update","delete"]),Or=1e3;function Dr(t){return ct.has(t)}function ht(t){try{return JSON.parse(t),!0}catch{return!1}}function be(t){return t.filter(e=>typeof e=="string")}function kn(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)xr.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)$r(t,r)})()}function $r(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(_=>_.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",c=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.due_tz=="string"?r.due_tz:null,g=typeof r.recurrence=="string"?r.recurrence:null,y=JSON.stringify(Array.isArray(r.blocked_by)?be(r.blocked_by):[]),E=JSON.stringify(Array.isArray(r.labels)?be(r.labels):[]),x=JSON.stringify(r.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:{}),D=[...Array.isArray(r.notes)?be(r.notes):[]],T=null,$=o.timestamp,O=null;for(let _ of n)_.op_type==="update"&&(!O||_.timestamp>O)&&(O=_.timestamp);let j={};for(let _ of n)if(_.op_type!=="create"){if(_.op_type==="delete"){O!==null&&O>=_.timestamp||(T=_.timestamp,_.timestamp>$&&($=_.timestamp));continue}if(_.op_type==="update"){let F=_.field;if(!F)continue;if(F==="notes_clear"){D.length=0,_.timestamp>$&&($=_.timestamp),T&&_.timestamp>=T&&(T=null);continue}if(F==="notes"){if(_.value&&_.value.trim().length>0){let l=_.value.trim();D.length<Or&&!D.some(i=>i.trim()===l)&&D.push(l)}_.timestamp>$&&($=_.timestamp),T&&_.timestamp>=T&&(T=null);continue}if(!Dr(F))continue;let ne=j[F];if(ne&&(_.timestamp<ne.timestamp||_.timestamp===ne.timestamp&&_.id<ne.id))continue;let C=!1;switch(F){case"title":typeof _.value=="string"&&(s=_.value,C=!0);break;case"status":_.value&&oe.has(_.value)&&(a=_.value,C=!0);break;case"priority":_.value&&se.has(_.value)&&(c=_.value,C=!0);break;case"owner":p=_.value&&_.value.trim().length>0?_.value:null,C=!0;break;case"due_at":m=_.value&&_.value.trim().length>0?_.value:null,C=!0;break;case"due_tz":d=_.value&&_.value.trim().length>0?_.value:null,C=!0;break;case"blocked_by":if(_.value&&ht(_.value)){let l=JSON.parse(_.value);Array.isArray(l)&&(y=JSON.stringify(be(l)),C=!0)}break;case"labels":if(_.value&&ht(_.value)){let l=JSON.parse(_.value);Array.isArray(l)&&(E=JSON.stringify(be(l)),C=!0)}break;case"metadata":if(_.value&&ht(_.value)){let l=JSON.parse(_.value);typeof l=="object"&&l!==null&&!Array.isArray(l)&&(x=_.value,C=!0)}break;case"recurrence":g=_.value&&_.value.trim().length>0?_.value:null,C=!0;break}C&&(j[F]={timestamp:_.timestamp,id:_.id}),_.timestamp>$&&($=_.timestamp),T&&_.timestamp>=T&&(T=null)}}t.prepare(`INSERT INTO tasks (id, title, status, priority, owner, due_at, due_tz, recurrence, blocked_by, labels, notes, metadata, created_at, updated_at, deleted_at)
114
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
115
115
  ON CONFLICT(id) DO UPDATE SET
116
116
  title = excluded.title,
117
117
  status = excluded.status,
118
118
  priority = excluded.priority,
119
119
  owner = excluded.owner,
120
120
  due_at = excluded.due_at,
121
+ due_tz = excluded.due_tz,
121
122
  recurrence = excluded.recurrence,
122
123
  blocked_by = excluded.blocked_by,
123
124
  labels = excluded.labels,
124
125
  notes = excluded.notes,
125
126
  metadata = excluded.metadata,
126
127
  updated_at = excluded.updated_at,
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+=`
128
+ deleted_at = excluded.deleted_at`).run(e,s,a,c,p,m,d,g,y,E,JSON.stringify(D),x,o.timestamp,$,T)}var Nr=1e3,Xe=class{constructor(e,n,o,r=Nr){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 (?, ?)
129
+ 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;kn(this.db,s.entries),s.cursor&&this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
130
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(`pull_cursor_${this.deviceId}`,s.cursor);let a=s.entries.filter(c=>c.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 vn(t,e,n){let o=t.name,r=Ir.join(Rr.tmpdir(),`oru-sync-backup-${Date.now()}.db`);t.exec(`VACUUM INTO '${r.replace(/'/g,"''")}'`);try{return await new Xe(t,e,n).sync()}catch(s){t.close();for(let a of["-wal","-shm"])try{Tt.unlinkSync(o+a)}catch{}throw Tt.copyFileSync(r,o),s}finally{e.close?.();try{Tt.unlinkSync(r)}catch{}}}function wn(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}wt();var Fr={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},xn={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 Et(t,e="mdy",n="monday",o="same_day",r){let s=r??new Date,a=t.trim();if(!a)return null;let{datePart:c,timePart:p}=jr(a),m=Br(c,e,n,o,s);if(!m)return null;if(p){let d=Wr(p);if(!d)return null;m.setHours(d.hours,d.minutes,0,0)}return Jr(m)}function jr(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 Yr(o)?{datePart:n.slice(0,-1).join(" "),timePart:o}:{datePart:t,timePart:null}}function Yr(t){return/^\d{1,2}:\d{2}\s*(am?|pm?)?$/i.test(t)||/^\d{1,2}\s*(am?|pm?)$/i.test(t)}function Br(t,e,n,o,r){let s=t.toLowerCase().trim();if(s==="today"||s==="tod")return K(r);if(s==="tomorrow"||s==="tom"){let m=K(r);return m.setDate(m.getDate()+1),m}if(s==="tonight"){let m=K(r);return m.setHours(18,0,0,0),m}if(s==="next week")return Nn(r,ye[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=(ye[n]+6)%7,d=K(r),g=d.getDay(),y=(m-g+7)%7;return y===0||d.setDate(d.getDate()+y),d}{let m=s;m.startsWith("next ")&&(m=m.slice(5));let d=Fr[m];if(d!==void 0)return Nn(r,d)}{let m=s.match(/^(\d{1,2})(?:st|nd|rd|th)$/);if(m){let d=Number(m[1]);if(d>=1&&d<=31)return zr(r,d)}}{let m=s.match(/^in\s+(\d+)\s+(days?|weeks?|months?)$/);if(m){let d=Number(m[1]),g=m[2],y=K(r);if(g.startsWith("day"))y.setDate(y.getDate()+d);else if(g.startsWith("week"))y.setDate(y.getDate()+d*7);else if(g.startsWith("month")){let E=y.getDate();y.setDate(1),y.setMonth(y.getMonth()+d);let x=new Date(y.getFullYear(),y.getMonth()+1,0).getDate();y.setDate(Math.min(E,x))}return y}}{let m=s.match(/^([a-z]+)\s+(\d+)(?:st|nd|rd|th)?$/);if(m){let d=xn[m[1]];if(d!==void 0){let g=Number(m[2]);return Dn(r,d,g)}}}{let m=s.match(/^(\d+)(?:st|nd|rd|th)?\s+([a-z]+)$/);if(m){let d=xn[m[2]];if(d!==void 0){let g=Number(m[1]);return Dn(r,d,g)}}}let a=t.match(/^(\d{4})-(\d{2})-(\d{2})$/);if(a)return te(Number(a[1]),Number(a[2]),Number(a[3]));let c=t.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);if(c){let m=Number(c[1]),d=Number(c[2]),g=Number(c[3]);return On(m,d,g,e)}let p=t.match(/^(\d{1,2})\/(\d{1,2})$/);if(p){let m=Number(p[1]),d=Number(p[2]);return On(m,d,r.getFullYear(),e)}return null}function On(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 Dn(t,e,n){let o=t.getFullYear(),r=te(o,e,n);return r?r<K(t)?te(o+1,e,n):r:null}function Wr(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=$n(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=$n(r,o[2]),r<0||r>23?null:{hours:r,minutes:0}}return null}function $n(t,e){let n=e.startsWith("p");return n&&t<12?t+12:!n&&t===12?0:t}function Jr(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 K(t){return new Date(t.getFullYear(),t.getMonth(),t.getDate())}function zr(t,e){for(let n=0;n<12;n++){let o=new Date(t.getFullYear(),t.getMonth()+n,e);if(o.getDate()===e&&o>=K(t))return o}return null}function Nn(t,e){let n=K(t),o=n.getDay(),r=e-o;return r<=0&&(r+=7),n.setDate(n.getDate()+r),n}import Ot from"fs";import qr from"path";import Kr from"os";import{spawn as Gr}from"child_process";import{stringify as Qr,parse as Zr}from"smol-toml";var Hr=new Set(["DAILY","WEEKLY","MONTHLY","YEARLY"]),Vr=new Set(["MO","TU","WE","TH","FR","SA","SU"]);function Se(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(!Hr.has(a))return!1;o=!0;break;case"INTERVAL":{let c=Number(a);if(!Number.isInteger(c)||c<1)return!1;break}case"BYDAY":for(let c of a.split(","))if(!Vr.has(c))return!1;break;case"BYMONTHDAY":{let c=Number(a);if(!Number.isInteger(c)||c<1||c>31)return!1;break}default:return!1}}return o}import{z as M}from"zod";var xt=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/,ue=1e3,de=1e4,qe=200,ke=100,ve=100,In=100,we=50,Ee=100,xe=5e3;function Ke(t){return t.replace(/[\r\n]+/g," ").trim()}function Rn(t,{required:e=!1}={}){return t.length===0?{valid:!1,message:e?"Title is required.":"Title cannot be empty."}:t.length>ue?{valid:!1,message:`Title exceeds maximum length of ${ue} characters.`}:{valid:!0}}function An(t){return t.length>de?{valid:!1,message:`Note exceeds maximum length of ${de} characters.`}:{valid:!0}}function Ln(t){for(let e of t){if(e.length===0)return{valid:!1,message:"Label cannot be empty."};if(e.length>qe)return{valid:!1,message:`Label exceeds maximum length of ${qe} characters.`}}return{valid:!0}}var ta=M.enum(I),na=M.enum(R),ra=M.string().min(1,"Title is required.").max(ue,`Title exceeds maximum length of ${ue} characters.`),oa=M.string().max(ue,`Title exceeds maximum length of ${ue} characters.`),sa=M.array(M.string().max(qe,`Label exceeds maximum length of ${qe} characters.`)).max(ke,`labels exceeds maximum of ${ke} items.`),aa=M.array(M.string().max(de,`Note exceeds maximum length of ${de} characters.`)).max(In,`notes exceeds maximum of ${In} items.`),ia=M.string().max(de,`Note exceeds maximum length of ${de} characters.`),la=M.array(M.string()).max(ve,`blocked_by exceeds maximum of ${ve} items.`),ca=M.record(M.string(),M.unknown()).refine(t=>Object.keys(t).length<=we,`Metadata exceeds maximum of ${we} keys.`).refine(t=>Object.keys(t).every(e=>e.length<=Ee),`Metadata key exceeds maximum length of ${Ee} characters.`).refine(t=>Object.values(t).every(e=>typeof e!="string"||e.length<=xe),`Metadata value exceeds maximum length of ${xe} characters.`),Xr=new Set(Intl.supportedValuesOf("timeZone"));function me(t){return Xr.has(t)}var ua=M.string().refine(t=>me(t),"Invalid IANA timezone.").nullable().optional(),da=M.string().regex(xt,"Invalid date format. Expected YYYY-MM-DD, YYYY-MM-DDTHH:MM, or YYYY-MM-DDTHH:MM:SS.").nullable().optional();function Mn(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.due_tz&&(e.due_tz=t.due_tz),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=`+++
131
+ `;if(n+=Qr(e),n+=`
131
132
  +++
132
133
  `,n+=`
133
134
  # Notes
134
135
  `,n+=`# Add new notes below. Delete lines to remove notes.
135
136
  `,t.notes.length>0){n+=`
136
137
  `;for(let o of t.notes)n+=`- ${o.replace(/\r?\n/g,"\\n")}
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
- `).filter(S=>S.startsWith("- ")).map(S=>S.slice(2).replace(/\\n/g,`
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
138
+ `}return n}function Cn(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=Zr(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 c=r.due;if(c===void 0||c==="")e.due_at!==null&&(s.due_at=null);else if(c instanceof Date){let T=c.toISOString().slice(0,10);T!==e.due_at&&(s.due_at=T)}else if(typeof c=="string"&&c!==e.due_at){if(!xt.test(c))throw new Error(`Invalid due date: ${c}. Expected format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS.`);if(isNaN(new Date(c).getTime()))throw new Error(`Invalid due date: ${c}. The date is not a valid calendar date.`);s.due_at=c}let p=r.due_tz;if(p===void 0||p==="")e.due_tz!==null&&(s.due_tz=null);else if(typeof p=="string"&&p!==e.due_tz){if(!me(p))throw new Error(`Invalid timezone: ${p}.`);s.due_tz=p}let m=r.recurrence;if(m===void 0||m==="")e.recurrence!==null&&e.recurrence!==void 0&&(s.recurrence=null);else if(typeof m=="string"&&m!==e.recurrence){if(!Se(m))throw new Error(`Invalid recurrence: ${m}.`);s.recurrence=m}if(Array.isArray(r.blocked_by)){let T=r.blocked_by.filter(O=>typeof O=="string");(T.length!==e.blocked_by.length||T.some((O,j)=>O!==e.blocked_by[j]))&&(s.blocked_by=T)}if(Array.isArray(r.labels)){let T=r.labels.filter(O=>typeof O=="string");(T.length!==e.labels.length||T.some((O,j)=>O!==e.labels[j]))&&(s.labels=T)}if(r.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)){let T=r.metadata,$=e.metadata;JSON.stringify(T)!==JSON.stringify($)&&(s.metadata=T)}else!r.metadata&&Object.keys(e.metadata).length>0&&(s.metadata={});let g=t.slice(n[0].length).split(`
139
+ `).filter(T=>T.startsWith("- ")).map(T=>T.slice(2).replace(/\\n/g,`
140
+ `)),y=new Set(e.notes),E=g.filter(T=>!y.has(T)),x=new Set(g),D=e.notes.some(T=>!x.has(T));return{fields:s,newNotes:E,removedNotes:D}}async function Un(t){let e=qr.join(Kr.tmpdir(),`oru-edit-${Date.now()}.toml`);Ot.writeFileSync(e,t);let o=(process.env.EDITOR||"vi").split(/\s+/),r=o.shift();return o.push(e),await new Promise((a,c)=>{let p=Gr(r,o,{stdio:"inherit"});p.on("exit",m=>{m===0?a():c(new Error(`Editor exited with code ${m}. No changes were saved.`))}),p.on("error",c)}),{edited:Ot.readFileSync(e,"utf-8"),tmpFile:e}}function Pn(t){try{Ot.unlinkSync(t)}catch{}}var eo={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 Ge(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(!Se(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(),g={DAY:"DAILY",WEEK:"WEEKLY",MONTH:"MONTHLY",YEAR:"YEARLY"}[m]??"YEARLY";return`${n}FREQ=${g};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 g=eo[d];if(!g)throw new Error(`Unknown day: ${d}. Use: monday, tuesday, ..., or mon, tue, ...`);m.push(g)}return`${n}FREQ=WEEKLY;BYDAY=${m.join(",")}`}let c=r.match(/^every\s+(\d+)(?:st|nd|rd|th)$/);if(c){let p=Number(c[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.`)}pe();async function Dt(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[]}pe();function Oe(){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
141
  # Install: oru completions bash
141
142
  # Print: oru completions bash --print
142
143
 
@@ -149,9 +150,9 @@ _oru_completions() {
149
150
  local filter_subcommands="list show add remove"${""}
150
151
  local telemetry_subcommands="status enable disable"
151
152
  local completion_shells="bash zsh fish"
152
- local status_values="${$.join(" ")}"
153
- local priority_values="${D.join(" ")}"
154
- local sort_values="${B.join(" ")}"
153
+ local status_values="${I.join(" ")}"
154
+ local priority_values="${R.join(" ")}"
155
+ local sort_values="${W.join(" ")}"
155
156
 
156
157
  # Determine the subcommand
157
158
  local subcmd=""
@@ -217,7 +218,7 @@ ${n}
217
218
  ;;
218
219
  add)
219
220
  if [[ "$cur" == -* ]]; then
220
- COMPREPLY=($(compgen -W "--id -s --status -p --priority -d --due --assign -l --label -b --blocked-by -n --note -r --repeat --meta --json --plaintext" -- "$cur"))
221
+ COMPREPLY=($(compgen -W "--id -s --status -p --priority -d --due --assign -l --label -b --blocked-by -n --note -r --repeat --tz --meta --json --plaintext" -- "$cur"))
221
222
  fi
222
223
  ;;
223
224
  update)
@@ -226,7 +227,7 @@ ${n}
226
227
  tasks=$(oru _complete tasks "$cur" 2>/dev/null | cut -f1)
227
228
  COMPREPLY=($(compgen -W "$tasks" -- "$cur"))
228
229
  else
229
- COMPREPLY=($(compgen -W "-t --title -s --status -p --priority -d --due --assign -l --label --unlabel -b --blocked-by --unblock -n --note --clear-notes -r --repeat --meta --json --plaintext" -- "$cur"))
230
+ COMPREPLY=($(compgen -W "-t --title -s --status -p --priority -d --due --assign -l --label --unlabel -b --blocked-by --unblock -n --note --clear-notes -r --repeat --tz --meta --json --plaintext" -- "$cur"))
230
231
  fi
231
232
  ;;
232
233
  edit)
@@ -266,7 +267,7 @@ ${n}
266
267
  }
267
268
 
268
269
  complete -F _oru_completions oru
269
- `}de();function xe(){return`#compdef oru
270
+ `}pe();function De(){return`#compdef oru
270
271
  # oru shell completions for zsh
271
272
  # Install: oru completions zsh
272
273
  # Print: oru completions zsh --print
@@ -296,13 +297,13 @@ _oru() {
296
297
  )
297
298
 
298
299
  local -a status_values
299
- status_values=(${$.join(" ")})
300
+ status_values=(${I.join(" ")})
300
301
 
301
302
  local -a priority_values
302
- priority_values=(${D.join(" ")})
303
+ priority_values=(${R.join(" ")})
303
304
 
304
305
  local -a sort_values
305
- sort_values=(${B.join(" ")})
306
+ sort_values=(${W.join(" ")})
306
307
 
307
308
  _arguments -C \\
308
309
  '1:command:->command' \\
@@ -325,6 +326,7 @@ _oru() {
325
326
  '(-b --blocked-by)'{-b,--blocked-by}'[Blocked by task ID]:task:' \\
326
327
  '(-n --note)'{-n,--note}'[Add a note]:note:' \\
327
328
  '(-r --repeat)'{-r,--repeat}'[Recurrence rule]:rule:' \\
329
+ '--tz[IANA timezone for due date]:timezone:' \\
328
330
  '--meta[Metadata key=value]:meta:' \\
329
331
  '--json[Output as JSON]' \\
330
332
  '--plaintext[Output as plain text]' \\
@@ -383,6 +385,7 @@ _oru() {
383
385
  '(-n --note)'{-n,--note}'[Append a note]:note:' \\
384
386
  '--clear-notes[Remove all notes]' \\
385
387
  '(-r --repeat)'{-r,--repeat}'[Recurrence rule]:rule:' \\
388
+ '--tz[IANA timezone for due date]:timezone:' \\
386
389
  '--meta[Metadata key=value]:meta:' \\
387
390
  '--json[Output as JSON]' \\
388
391
  '--plaintext[Output as plain text]' \\
@@ -494,7 +497,7 @@ ${""} completions)
494
497
  }
495
498
 
496
499
  _oru "$@"
497
- `}de();function Oe(){return`# oru shell completions for fish
500
+ `}pe();function $e(){return`# oru shell completions for fish
498
501
  # Install: oru completions fish
499
502
  # Print: oru completions fish --print
500
503
 
@@ -558,13 +561,13 @@ complete -c oru -n '__oru_using_command filter' -a list -d 'List all saved filte
558
561
  complete -c oru -n '__oru_using_command filter' -a show -d 'Show a filter definition'
559
562
  complete -c oru -n '__oru_using_command filter' -a add -d 'Save a new named filter'
560
563
  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 '${$.join(" ")}' -d 'Status' -r
562
- complete -c oru -n '__oru_using_subcommand filter add' -s p -l priority -a '${D.join(" ")}' -d 'Priority' -r
564
+ complete -c oru -n '__oru_using_subcommand filter add' -s s -l status -a '${I.join(" ")}' -d 'Status' -r
565
+ complete -c oru -n '__oru_using_subcommand filter add' -s p -l priority -a '${R.join(" ")}' -d 'Priority' -r
563
566
  complete -c oru -n '__oru_using_subcommand filter add' -s l -l label -a '(__oru_labels)' -d 'Label' -r
564
567
  complete -c oru -n '__oru_using_subcommand filter add' -l owner -d 'Filter by owner' -r
565
568
  complete -c oru -n '__oru_using_subcommand filter add' -l due -d 'Filter by due date' -r
566
569
  complete -c oru -n '__oru_using_subcommand filter add' -l overdue -d 'Show only overdue tasks'
567
- complete -c oru -n '__oru_using_subcommand filter add' -l sort -a '${B.join(" ")}' -d 'Sort order' -r
570
+ complete -c oru -n '__oru_using_subcommand filter add' -l sort -a '${W.join(" ")}' -d 'Sort order' -r
568
571
  complete -c oru -n '__oru_using_subcommand filter add' -l search -d 'Search by title' -r
569
572
  complete -c oru -n '__oru_using_subcommand filter add' -s a -l all -d 'Include done tasks'
570
573
  complete -c oru -n '__oru_using_subcommand filter add' -l actionable -d 'Show only actionable tasks'
@@ -598,14 +601,14 @@ complete -c oru -n '__oru_using_command backup' -a '(__fish_complete_directories
598
601
  complete -c oru -n '__oru_using_command sync' -F
599
602
 
600
603
  # Status flag for add, list, update, edit
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
+ complete -c oru -n '__oru_using_command add' -s s -l status -a '${I.join(" ")}' -d 'Status' -r
605
+ complete -c oru -n '__oru_using_command list' -s s -l status -a '${I.join(" ")}' -d 'Status' -r
606
+ complete -c oru -n '__oru_using_command update' -s s -l status -a '${I.join(" ")}' -d 'Status' -r
604
607
 
605
608
  # Priority flag for add, list, update, edit
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
+ complete -c oru -n '__oru_using_command add' -s p -l priority -a '${R.join(" ")}' -d 'Priority' -r
610
+ complete -c oru -n '__oru_using_command list' -s p -l priority -a '${R.join(" ")}' -d 'Priority' -r
611
+ complete -c oru -n '__oru_using_command update' -s p -l priority -a '${R.join(" ")}' -d 'Priority' -r
609
612
 
610
613
  # Label flag
611
614
  complete -c oru -n '__oru_using_command add' -s l -l label -a '(__oru_labels)' -d 'Label' -r
@@ -625,13 +628,14 @@ complete -c oru -n '__oru_using_command add' -l assign -d 'Assign to owner' -r
625
628
  complete -c oru -n '__oru_using_command add' -s b -l blocked-by -d 'Blocked by task ID' -r
626
629
  complete -c oru -n '__oru_using_command add' -l meta -d 'Metadata key=value' -r
627
630
  complete -c oru -n '__oru_using_command add' -s r -l repeat -d 'Recurrence rule' -r
631
+ complete -c oru -n '__oru_using_command add' -l tz -d 'IANA timezone for due date' -r
628
632
  complete -c oru -n '__oru_using_command list' -l json -d 'Output as JSON'
629
633
  complete -c oru -n '__oru_using_command list' -l plaintext -d 'Output as plain text'
630
634
  complete -c oru -n '__oru_using_command list' -l search -d 'Search by title' -r
631
635
  complete -c oru -n '__oru_using_command list' -l owner -d 'Filter by owner' -r
632
636
  complete -c oru -n '__oru_using_command list' -l due -d 'Filter by due date' -r
633
637
  complete -c oru -n '__oru_using_command list' -l overdue -d 'Show only overdue tasks'
634
- complete -c oru -n '__oru_using_command list' -l sort -a '${B.join(" ")}' -d 'Sort order' -r
638
+ complete -c oru -n '__oru_using_command list' -l sort -a '${W.join(" ")}' -d 'Sort order' -r
635
639
  complete -c oru -n '__oru_using_command list' -s a -l all -d 'Include done tasks'
636
640
  complete -c oru -n '__oru_using_command list' -l actionable -d 'Show only actionable tasks'
637
641
  complete -c oru -n '__oru_using_command list' -l limit -d 'Maximum number of tasks' -r
@@ -650,6 +654,7 @@ complete -c oru -n '__oru_using_command update' -s b -l blocked-by -d 'Blocked b
650
654
  complete -c oru -n '__oru_using_command update' -l unblock -d 'Remove blocker task ID' -r
651
655
  complete -c oru -n '__oru_using_command update' -l meta -d 'Metadata key=value' -r
652
656
  complete -c oru -n '__oru_using_command update' -s r -l repeat -d 'Recurrence rule' -r
657
+ complete -c oru -n '__oru_using_command update' -l tz -d 'IANA timezone for due date' -r
653
658
  complete -c oru -n '__oru_using_command edit' -l json -d 'Output as JSON'
654
659
  complete -c oru -n '__oru_using_command edit' -l plaintext -d 'Output as plain text'
655
660
  complete -c oru -n '__oru_using_command delete' -l json -d 'Output as JSON'
@@ -671,79 +676,81 @@ complete -c oru -n '__oru_using_command sync' -l plaintext -d 'Output as plain t
671
676
 
672
677
  # self-update flags
673
678
  complete -c oru -n '__oru_using_command self-update' -l check -d 'Only check if an update is available'
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(`
679
+ `}import Qe from"fs";import G from"path";import to from"os";import no from"readline";function $t(){let t=process.env.SHELL;if(!t)return null;let e=G.basename(t);return e==="bash"||e==="zsh"||e==="fish"?e:null}function Fn(t,e=to.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 ro(t){switch(t){case"bash":return Oe();case"zsh":return De();case"fish":return $e()}}function Ze(t,e,n){let{scriptPath:o,rcPath:r}=Fn(t,n);Qe.mkdirSync(G.dirname(o),{recursive:!0}),Qe.writeFileSync(o,ro(t)),e(`Wrote completions to ${o}`);let s=!1;if(r){let a=`source ${o}`,c=`source ~/.oru/completions.${t}`,p="";try{p=Qe.readFileSync(r,"utf-8")}catch{}if(!p.includes(a)&&!p.includes(c)){let m=p.length>0&&!p.endsWith(`
675
680
  `)?`
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?`
681
+ `:"";Qe.appendFileSync(r,`${m}${a}
682
+ `),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 Nt(t,e=process.stdin,n=process.stdout){return new Promise(o=>{let r=no.createInterface({input:e,output:n});r.question(t,s=>{r.close();let a=s.trim().toLowerCase();o(a===""||a==="y"||a==="yes")})})}function et(t){return t.rcPath?`
678
683
  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(`
684
+ Completions will be loaded automatically on next shell start.`}import tt from"fs";import It from"path";import oo from"os";import{parse as so}from"smol-toml";var Rt=["status","priority","owner","label","search","sort","actionable","due","overdue","all","limit","offset"];function jn(){return process.env.ORU_CONFIG_DIR?It.join(process.env.ORU_CONFIG_DIR,"filters.toml"):It.join(oo.homedir(),".oru","filters.toml")}function fe(t){let e=t??jn();if(!tt.existsSync(e))return{};let n=tt.readFileSync(e,"utf-8");try{return so(n)}catch(o){return process.stderr.write(`Warning: Could not parse filters file at ${e}: ${o instanceof Error?o.message:String(o)}. Ignoring.
685
+ `),{}}}function ao(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 io(t){return`["${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"]`}function lo(t){let e=[];for(let[n,o]of Object.entries(t)){let r=[io(n)];for(let[s,a]of Object.entries(o))a!==void 0&&r.push(`${s} = ${ao(a)}`);e.push(r.join(`
681
686
  `))}return e.join(`
682
687
 
683
688
  `)+(e.length>0?`
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
689
+ `:"")}function At(t,e){let n=e??jn();tt.mkdirSync(It.dirname(n),{recursive:!0}),tt.writeFileSync(n,lo(t))}function Yn(t,e){let n={...t};for(let o of Rt)t[o]===void 0&&e[o]!==void 0&&(n[o]=e[o]);return n}nt();Ut();Ne();function Zn(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 er(t){return t.replace(/\b(update -s \w+)\b/g,(e,n)=>_e(n))}function Lo(t){let e="";return t.split(`
690
+ `).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,c]=o;return s.startsWith("-")?r+L(s)+a+c:r+_e(s)+a+v(er(c))}return e==="commands"&&n.match(/^\s{4,}\S/)?v(er(n)):n}).join(`
691
+ `)}var Mo={formatHelp(t,e){let n=Ao.prototype.formatHelp.call(e,t,e);return Lo(n)}};function tr(t){t.configureHelp(Mo);for(let e of t.commands)tr(e)}function Co(t,e=r=>process.stdout.write(`${r}
692
+ `),n,o=r=>process.stderr.write(r)){let r=n??vt(),s=dn(t),a=wn(t),c=new He(s,a),p=new Ro("oru").description(`${L("oru")} - personal task manager that your agents can operate for you
688
693
 
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",`
694
+ 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(`${z} (${Bn})`);p.configureOutput({writeOut:e,writeErr:e}),p.exitOverride();function m(l){return l.plaintext?!1:!!(l.json||process.env.ORU_FORMAT==="json"||r.output_format==="json")}function d(l,i){e(i?JSON.stringify({error:"ambiguous_prefix",id:l.prefix,matches:l.matches}):`Prefix '${l.prefix}' is ambiguous, matches: ${l.matches.join(", ")}.`),process.exitCode=1}function g(l,i){e(l?JSON.stringify({error:"validation",message:i}):i),process.exitCode=1}function y(l,i){e(l?JSON.stringify({error:"not_found",id:i}):`Task ${i} not found.`),process.exitCode=1}function E(l,i){let u=Rn(l);return u.valid?!0:(g(i,u.message),!1)}function x(l,i){let u=An(l);return u.valid?!0:(g(i,u.message),!1)}function D(l,i){if(l.length>ke)return g(i,`labels exceeds maximum of ${ke} items.`),!1;let u=Ln(l);return u.valid?!0:(g(i,u.message),!1)}async function T(l,i,u,f){try{let b=await u();if(!b){y(i,l);return}f(b)}catch(b){if(b instanceof ee){d(b,i);return}throw b}}function $(l,i){return l.length>ve?(g(i,`blocked_by exceeds maximum of ${ve} items.`),!1):!0}function O(l,i){if(Object.keys(l).length>we)return g(i,`Metadata exceeds maximum of ${we} keys.`),!1;for(let u of Object.keys(l))if(u.length>Ee)return g(i,`Metadata key exceeds maximum length of ${Ee} characters.`),!1;for(let u of Object.values(l))if(typeof u=="string"&&u.length>xe)return g(i,`Metadata value exceeds maximum length of ${xe} 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(I).default("todo")).addOption(new Z("-p, --priority <priority>","Priority level").choices(R).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("--tz <timezone>","IANA timezone for due date (e.g. America/New_York)").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
695
  Examples:
691
696
  $ oru add "Fix login bug"
692
697
  $ oru add "Fix login bug" -p high -d friday
693
698
  $ oru add "Write docs" -l docs -n "Include API section"
694
699
  $ 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=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",`
700
+ $ oru add "Water plants" -r "every 3 days" -d today
701
+ $ oru add "London sync" -d "monday 3pm" --tz Europe/London`).action(async(l,i)=>{l=Ke(l);let u=m(i);if(!E(l,u)||i.note&&!x(i.note,u)||i.label&&!D(i.label,u)||i.blockedBy&&!$(i.blockedBy,u))return;if(i.blockedBy){let N=await c.validateBlockedBy(null,i.blockedBy);if(!N.valid){g(u,N.error);return}}if(i.id&&!qt(i.id)){g(u,`Invalid ID format: "${i.id}". IDs must be 11-character base62 strings.`);return}if(i.id){let N=await c.get(i.id);if(N){e(u?V(N):J(N));return}}let f;if(i.due){let N=Et(i.due,r.date_format,r.first_day_of_week,r.next_month);if(!N){g(u,`Could not parse due date: ${i.due}. Try: today, tomorrow, next friday, 2026-03-01, or march 15.`);return}f=N}let b;if(i.repeat)try{b=Ge(i.repeat)}catch(N){g(u,N instanceof Error?N.message:String(N));return}let k=i.meta?Zn(i.meta):void 0;if(k&&!O(k,u))return;let S;if(i.tz){if(!me(i.tz)){g(u,`Invalid timezone: "${i.tz}".`);return}S=i.tz}let h=i.assign!==void 0&&i.assign.trim()===""?null:i.assign,w=await c.add({title:l,id:i.id,status:i.status,priority:i.priority,owner:h,due_at:f,due_tz:S,recurrence:b,blocked_by:i.blockedBy,labels:i.label??void 0,notes:i.note?[i.note]:void 0,metadata:k});e(u?V(w):J(w))}),p.command("list").description("List tasks (hides done tasks by default)").option("-s, --status <status>","Filter by status (comma-separated for multiple)",l=>{let i=l.split(",");for(let u of i)if(!I.includes(u))throw new Error(`Invalid status: ${u}. Allowed: ${I.join(", ")}`);return i.length===1?i[0]:i}).option("-p, --priority <priority>","Filter by priority (comma-separated for multiple)",l=>{let i=l.split(",");for(let u of i)if(!R.includes(u))throw new Error(`Invalid priority: ${u}. Allowed: ${R.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(W)).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
702
  Examples:
697
703
  $ oru list
698
704
  $ oru list -s in_progress -p high
699
705
  $ oru list -l backend --sort due --actionable
700
706
  $ 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=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",`
707
+ $ oru list --filter mine`).action(async l=>{let i=m(l),u=l,f;if(l.filter){let S=fe()[l.filter];if(!S){g(i,`Filter '${l.filter}' not found. Run 'oru filter list' to see available filters.`);return}u={...l,...Yn(l,S)},f=S.sql}let b=await c.list({status:u.status,priority:u.priority,owner:u.owner,label:u.label,search:u.search,sort:u.sort,actionable:u.actionable,limit:u.limit,offset:u.offset,sql:f});b=Zt(b,u),u.due&&(b=ft(b,u.due,void 0,r.first_day_of_week)),u.overdue&&(b=ft(b,"overdue")),e(i?bn(b):pt(b))}),p.command("labels").description("List all labels in use").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(async l=>{let i=await c.listLabels(),u=m(l);e(u?hn(i):an(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
708
  Examples:
703
709
  $ oru get 019414a3
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",`
710
+ $ oru get 019414a3 --json`).action(async(l,i)=>{let u=m(i);await T(l,u,()=>c.get(l),f=>{e(u?V(f):J(f))})}),p.command("update <id>").description("Update a task").option("-t, --title <title>","New title").addOption(new Z("-s, --status <status>","New status").choices(I)).addOption(new Z("-p, --priority <priority>","New priority").choices(R)).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("--tz <timezone>","IANA timezone for due date (e.g. America/New_York, '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
711
  Examples:
706
712
  $ oru update 019414a3 -s in_progress
707
713
  $ oru update 019414a3 -l urgent -d tomorrow
708
714
  $ oru update 019414a3 -n "Blocked on API review"
709
715
  $ 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=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",`
716
+ $ oru update 019414a3 -r "every monday"
717
+ $ oru update 019414a3 --tz America/New_York`).action(async(l,i)=>{let u=m(i);if(i.title!==void 0&&(i.title=Ke(i.title)),!(i.title!==void 0&&!E(i.title,u))&&!(i.note&&!x(i.note,u))&&!(i.label&&!D(i.label,u))&&!(i.blockedBy&&!$(i.blockedBy,u))){if(i.blockedBy){let f=await c.validateBlockedBy(l,i.blockedBy);if(!f.valid){g(u,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 S=Et(i.due,r.date_format,r.first_day_of_week,r.next_month);if(!S){g(u,`Could not parse due date: ${i.due}. Try: today, tomorrow, next friday, 2026-03-01, or march 15.`);return}f.due_at=S}if(i.assign!==void 0&&(i.assign.toLowerCase()==="none"||i.assign.trim()===""?f.owner=null:f.owner=i.assign),i.label||i.unlabel){let S=await c.get(l);if(!S){y(u,l);return}let h=[...S.labels];if(i.label)for(let w of i.label)h.includes(w)||h.push(w);if(i.unlabel&&(h=h.filter(w=>!i.unlabel.includes(w))),!D(h,u))return;f.labels=h}if(i.blockedBy||i.unblock){let S;if(i.blockedBy)S=[...i.blockedBy];else{let h=await c.get(l);if(!h){y(u,l);return}S=[...h.blocked_by]}if(i.unblock&&(S=S.filter(h=>!i.unblock.includes(h))),!$(S,u))return;f.blocked_by=S}if(i.repeat!==void 0)if(i.repeat.toLowerCase()==="none")f.recurrence=null;else try{f.recurrence=Ge(i.repeat)}catch(S){g(u,S instanceof Error?S.message:String(S));return}if(i.tz!==void 0)if(i.tz.toLowerCase()==="none")f.due_tz=null;else{if(!me(i.tz)){g(u,`Invalid timezone: "${i.tz}".`);return}f.due_tz=i.tz}if(i.meta){let S=await c.get(l);if(!S){y(u,l);return}let h=Zn(i.meta),w={...S.metadata};for(let[N,X]of Object.entries(h))X===null?delete w[N]:w[N]=X;if(!O(w,u))return;f.metadata=w}let b=Object.keys(f).length>0,k;if(i.clearNotes)k=await c.clearNotesAndUpdate(l,f,i.note);else if(i.note&&b)k=await c.updateWithNote(l,f,i.note);else if(i.note)k=await c.addNote(l,i.note);else if(b)k=await c.update(l,f);else{if(!u){e("No changes.");return}k=await c.get(l)}if(!k){y(u,l);return}if(e(u?V(k):J(k)),k.status==="done"&&k.recurrence){let S=await c.getSpawnedTask(k.id);S&&(u?e(JSON.stringify({spawned:S},null,2)):(e(`
718
+ ${v("Next occurrence:")} ${ae(k.recurrence)}`),e(J(S))))}}catch(f){if(f instanceof ee){d(f,u);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
719
  Examples:
713
720
  $ 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 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",`
721
+ $ EDITOR=nano oru edit 019414a3`).action(async(l,i)=>{let u=m(i);try{let f=await c.get(l);if(!f){y(u,l);return}let b=Mn(f),{edited:k,tmpFile:S}=await Un(b),h,w,N;try{({fields:h,newNotes:w,removedNotes:N}=Cn(k,f))}catch(B){let zt=B instanceof Error?B.message:String(B);g(u,zt),o(`Your edits are saved at: ${S}
722
+ `);return}Pn(S);let X=Object.keys(h).length>0,at=w.length>0;if(!X&&!at&&!N){e(u?V(f):"No changes.");return}if(h.title!==void 0&&(h.title=Ke(h.title)),h.title!==void 0&&!E(h.title,u))return;for(let B of w)if(!x(B,u))return;if(h.labels&&!D(h.labels,u)||h.blocked_by&&!$(h.blocked_by,u))return;if(h.blocked_by){let B=await c.validateBlockedBy(f.id,h.blocked_by);if(!B.valid){g(u,B.error);return}}if(h.metadata&&!O(h.metadata,u))return;let Y;if(N){let Ht=[...k.slice(k.indexOf("+++",3)+3).split(`
723
+ `).filter(it=>it.startsWith("- ")).map(it=>it.slice(2)),...w];Ht.length===0?Y=await c.clearNotes(l):Y=await c.replaceNotes(l,Ht),X&&(Y=await c.update(l,h))}else if(at&&w.length===1&&X)Y=await c.updateWithNote(l,h,w[0]);else if(at&&w.length===1&&!X)Y=await c.addNote(l,w[0]);else{X&&(Y=await c.update(l,h));for(let B of w)Y=await c.addNote(l,B)}Y||(Y=await c.get(l)),e(u?V(Y):J(Y))}catch(f){if(f instanceof ee){d(f,u);return}throw f}});function j(l,i,u,f){p.command(`${l} <id...>`).description(u).option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",f).action(async(b,k)=>{let S=m(k);for(let h of b)await T(h,S,()=>c.update(h,{status:i}),w=>{e(S?V(w):J(w))})})}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
724
  Examples:
718
725
  $ oru done 019414a3
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)",`
726
+ $ oru done 019414a3 019414b7`).action(async(l,i)=>{let u=m(i);for(let f of l)await T(f,u,()=>c.update(f,{status:"done"}),async b=>{if(e(u?V(b):J(b)),b.recurrence){let k=await c.getSpawnedTask(b.id);k&&(u?e(JSON.stringify({spawned:k},null,2)):(e(`
727
+ ${v("Next occurrence:")} ${ae(b.recurrence)}`),e(J(k))))}})}),j("start","in_progress","Start one or more tasks (shortcut for update -s in_progress)",`
721
728
  Examples:
722
729
  $ oru start 019414a3
723
- $ oru start 019414a3 019414b7`),g("review","in_review","Mark one or more tasks as in_review (shortcut for update -s in_review)",`
730
+ $ oru start 019414a3 019414b7`),j("review","in_review","Mark one or more tasks as in_review (shortcut for update -s in_review)",`
724
731
  Examples:
725
732
  $ oru review 019414a3
726
733
  $ oru review 019414a3 019414b7`),p.command("context").description("Show a summary of what needs your attention (overdue, due soon, in progress, actionable, blocked, recently completed)").option("--owner <owner>","Scope to a specific owner").option("-l, --label <label>","Filter by label").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").addHelpText("after",`
727
734
  Examples:
728
735
  $ oru context
729
736
  $ 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?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",`
737
+ $ oru context -l backend`).action(async l=>{let{sections:i}=await c.getContext({owner:l.owner,label:l.label}),u=m(l);e(u?yn(i):cn(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
738
  Examples:
732
739
  $ oru delete 019414a3
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",`
740
+ $ oru delete 019414a3 019414b7`).action(async(l,i)=>{let u=m(i);for(let f of l)await T(f,u,()=>c.delete(f),()=>{e(u?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
741
  Examples:
735
742
  $ oru log 019414a3
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",`
743
+ $ oru log 019414a3 --json`).action(async(l,i)=>{let u=m(i);await T(l,u,()=>c.log(l),f=>{e(u?Tn(f):ln(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
744
  Examples:
738
745
  $ oru sync /mnt/shared/oru
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",`
746
+ $ oru sync ~/Dropbox/oru-sync`).action(async(l,i)=>{let u=new Ve(l);try{let f=await vn(t,u,a),b=m(i);e(b?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.
747
+ `),f}});let _=p.command("config").description("Manage configuration");_.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(l=>{let i=m(l),u=he();if(Wt.existsSync(u)){e(i?JSON.stringify({message:"Config file already exists.",path:u}):`Config file already exists at ${u}`);return}Wt.mkdirSync(Le.dirname(u),{recursive:!0}),Wt.writeFileSync(u,En),e(i?JSON.stringify({message:"Config file created.",path:u}):`Created ${u}`)}),_.command("path").description("Print the config file path").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(l=>{let i=m(l),u=he();e(i?JSON.stringify({path:u}):u)});let F=p.command("filter").description("Manage saved list filters");F.command("list").description("List all saved filters").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(l=>{let i=m(l),u=fe(),f=Object.keys(u);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 b of f)e(b)}),F.command("show <name>").description("Show a filter's definition").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((l,i)=>{let u=m(i),b=fe()[l];if(!b){e(u?JSON.stringify({error:"not_found",name:l}):`Filter '${l}' not found.`),process.exitCode=1;return}if(u){e(JSON.stringify({name:l,...b}));return}let k=[`[${l}]`];for(let[S,h]of Object.entries(b))h!==void 0&&(Array.isArray(h)?k.push(`${S} = ${h.join(", ")}`):k.push(`${S} = ${h}`));e(k.join(`
748
+ `))}),F.command("remove <name>").description("Delete a saved filter").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action((l,i)=>{let u=m(i),f=fe();if(!(l in f)){e(u?JSON.stringify({error:"not_found",name:l}):`Filter '${l}' not found.`),process.exitCode=1;return}delete f[l],At(f),e(u?JSON.stringify({message:`Removed filter '${l}'.`,name:l}):`Removed filter '${l}'.`)}),F.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)",l=>{let i=l.split(",");for(let u of i)if(!I.includes(u))throw new Error(`Invalid status: ${u}. Allowed: ${I.join(", ")}`);return i.length===1?i[0]:i}).option("-p, --priority <priority>","Filter by priority (comma-separated for multiple)",l=>{let i=l.split(",");for(let u of i)if(!R.includes(u))throw new Error(`Invalid priority: ${u}. Allowed: ${R.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(W)).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
749
  Examples:
743
750
  $ oru filter add mine --owner tchayen --status todo
744
751
  $ 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 $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
- 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 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};
752
+ $ oru filter add edge --sql "priority = 'urgent'"`).action((l,i)=>{let u=m(i),f={};for(let h of Rt)i[h]!==void 0&&(f[h]=i[h]);if(i.sql!==void 0&&(f.sql=i.sql),Object.keys(f).length===0){e(u?JSON.stringify({error:"validation",message:"No filter fields specified."}):"No filter fields specified. Pass at least one flag."),process.exitCode=1;return}let b=fe(),k=l in b;b[l]=f,At(b);let S=k?`Updated filter '${l}'.`:`Saved filter '${l}'.`;e(u?JSON.stringify({message:S,name:l,filter:f}):S)}),!1;let ne=p.command("completions").description("Generate shell completion scripts").action(async()=>{let l=$t();if(!l){e(`Could not detect shell from $SHELL.
753
+ Use: oru completions bash|zsh|fish`),process.exitCode=1;return}if(!process.stdin.isTTY){e(`Detected shell: ${l}
754
+ Run interactively: oru completions ${l}`),process.exitCode=1;return}if(e(`Detected shell: ${l}`),!await Nt(`Install completions for ${l}? [Y/n] `)){e("Aborted.");return}let u=Ze(l,e);e(et(u))});for(let l of["bash","zsh","fish"])ne.command(l).description(`Install ${l} completions`).option("--print","Print the completion script to stdout instead of installing").action(i=>{if(i.print||!process.stdout.isTTY){e(l==="bash"?Oe():l==="zsh"?De():$e());return}let u=Ze(l,e);e(et(u))});p.command("self-update").description("Update oru to the latest version").option("--check","Only check if an update is available").action(async l=>{let{performUpdate:i}=await Promise.resolve().then(()=>(Gn(),Kn));await i(!!l.check)});let C=p.command("telemetry").description("Manage anonymous usage telemetry");return C.command("status").description("Show whether telemetry is enabled or disabled").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(l=>{let i=m(l),u=Mt(r);e(i?JSON.stringify({enabled:!u,...u?{reason:u}:{}}):u?`Telemetry: ${u}`:"Telemetry: enabled")}),C.command("enable").description("Enable anonymous usage telemetry").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(l=>{let i=m(l);Te("telemetry","true"),e(i?JSON.stringify({enabled:!0}):"Telemetry enabled.")}),C.command("disable").description("Disable anonymous usage telemetry").option("--json","Output as JSON").option("--plaintext","Output as plain text (overrides config)").action(l=>{let i=m(l);Te("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((l,i)=>{let u=m(i),f=l??r.backup_path;if(!f){e(u?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 b=rt(t,f);e(u?JSON.stringify({path:b}):`Backed up to ${b}`)}),p.command("mcp").description("Start the MCP (Model Context Protocol) server over stdio").action(async()=>{let l=Le.dirname(Jt(import.meta.url)),i=Le.join(l,"mcp","index.js"),u=Qn(process.execPath,[i],{stdio:"inherit",env:process.env});await new Promise(f=>{u.on("exit",b=>{b!==null&&(process.exitCode=b),f()}),u.on("error",b=>{process.stderr.write(`Failed to start MCP server: ${b.message}
755
+ `),process.exitCode=1,f()})})}),p.command("_complete <type> [prefix]",{hidden:!0}).action(async(l,i)=>{let u=await Dt(c,l,i??"");u.length>0&&e(u.join(`
756
+ `))}),tr(p),p}async function Uo(){let t=Date.now(),e=pn();gn(e);let n=vt(),o=Co(e,void 0,n);if(n.backup_path){let{autoBackup:a}=await Promise.resolve().then(()=>(Ut(),Hn));a(e,n.backup_path,n.backup_interval)}try{let{showFirstRunNotice:a}=await Promise.resolve().then(()=>(nt(),Ct));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(()=>(st(),Yt));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:c,sendEvent:p}=await Promise.resolve().then(()=>(nt(),Ct)),{command:m,flags:d}=a(process.argv);if(Lt(n)&&!m.startsWith("telemetry")){let g=Date.now()-t,y=c(m,d,g,Number(process.exitCode??0),s);p(y)}}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Telemetry send failed:",a)}if(r)try{let a=await r;if(a){let{printUpdateNotice:c}=await Promise.resolve().then(()=>(st(),Yt));c(a)}}catch(a){process.env.ORU_DEBUG==="1"&&console.error("Update check failed:",a)}}var Po=Jt(import.meta.url),Fo=process.argv[1]&&Po===process.argv[1];Fo&&Uo().catch(t=>{process.env.ORU_DEBUG==="1"?console.error(t):console.error(t instanceof Error?t.message:String(t)),process.exit(1)});export{Co as createProgram};
package/dist/mcp/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
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(`
2
+ import{StdioServerTransport as it}from"@modelcontextprotocol/sdk/server/stdio.js";import Ue from"better-sqlite3";import Z from"path";import Ae from"os";import C from"fs";function Re(){return process.env.ORU_DB_PATH?process.env.ORU_DB_PATH:Z.join(Ae.homedir(),".oru","oru.db")}function ee(t){let r=t??Re(),e=Z.dirname(r);C.existsSync(e)||C.mkdirSync(e,{recursive:!0,mode:448});let s=new Ue(r);s.pragma("journal_mode = WAL"),s.pragma("foreign_keys = ON");try{C.chmodSync(r,384)}catch{}return s}function $e(t){let r=t.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();return r?parseInt(r.value,10):0}function te(t,r){let e=$e(t),s=r.filter(o=>o.version>e).sort((o,a)=>o.version-a.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
+ `),t.transaction(()=>{for(let o of s)o.up(t),t.prepare("UPDATE meta SET value = ? WHERE key = 'schema_version'").run(String(o.version));return s.length})()}function re(t){t.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 He}from"@modelcontextprotocol/sdk/server/stdio.js
30
30
  );
31
31
 
32
32
  INSERT OR IGNORE INTO meta (key, value) VALUES ('schema_version', '1');
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 (
33
+ `),te(t,Me)}var Me=[{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")}},{version:8,up:t=>{t.exec("ALTER TABLE tasks ADD COLUMN due_tz TEXT")}}];import{Kysely as Ce,SqliteDialect as Fe}from"kysely";function ne(t){return new Ce({dialect:new Fe({database:t})})}import{randomBytes as Pe}from"crypto";var se="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",St=new Set(se),Be=11;function F(t,r){let e=0n;for(let n of t)e=e<<8n|BigInt(n);let s=[];for(let n=0;n<r;n++)s.push(se[Number(e%62n)]),e/=62n;return s.reverse().join("")}function k(){return F(Pe(8),Be)}function ie(t){let r=t.prepare("SELECT value FROM meta WHERE key = 'device_id'").get();if(r)return r.value;let e=k();return t.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('device_id', ?)").run(e),t.prepare("SELECT value FROM meta WHERE key = 'device_id'").get().value}import{sql as Ze}from"kysely";import{sql as y}from"kysely";var S=["todo","in_progress","in_review","done"],Ye=new Set(S),w=["low","medium","high","urgent"],We=new Set(w),oe=["title","status","priority","owner","due_at","due_tz","recurrence","blocked_by","labels","metadata"],je=new Set(oe);var ae="todo",le="medium";var P=class extends Error{prefix;matches;constructor(r,e){super(`Prefix '${r}' is ambiguous, matches: ${e.join(", ")}`),this.name="AmbiguousPrefixError",this.prefix=r,this.matches=e}};function E(t,r){try{return JSON.parse(t)}catch{return r}}function B(t){return{id:t.id,title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,due_tz:t.due_tz,recurrence:t.recurrence,blocked_by:E(t.blocked_by,[]),labels:E(t.labels,[]),notes:E(t.notes,[]),metadata:E(t.metadata,{}),created_at:t.created_at,updated_at:t.updated_at,deleted_at:t.deleted_at}}async function Y(t,r,e){let s=r.id??k(),n=e??new Date().toISOString(),i={id:s,title:r.title,status:r.status??ae,priority:r.priority??le,owner:r.owner??null,due_at:r.due_at??null,due_tz:r.due_tz??null,recurrence:r.recurrence??null,blocked_by:r.blocked_by??[],labels:r.labels??[],notes:r.notes??[],metadata:r.metadata??{},created_at:n,updated_at:n,deleted_at:null};return await t.insertInto("tasks").values({id:i.id,title:i.title,status:i.status,priority:i.priority,owner:i.owner,due_at:i.due_at,due_tz:i.due_tz,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 x(t,r){let e=t.selectFrom("tasks").selectAll().where("deleted_at","is",null);if(r?.status&&(Array.isArray(r.status)?e=e.where("status","in",r.status):e=e.where("status","=",r.status)),r?.priority&&(Array.isArray(r.priority)?e=e.where("priority","in",r.priority):e=e.where("priority","=",r.priority)),r?.owner&&(e=e.where("owner","=",r.owner)),r?.label){let i=r.label;e=e.where(y`EXISTS (SELECT 1 FROM json_each(labels) WHERE json_each.value = ${i})`)}if(r?.search){let i=r.search.replace(/[\\%_]/g,"\\$&");e=e.where(y`title LIKE '%' || ${i} || '%' ESCAPE '\\' COLLATE NOCASE`)}switch(r?.actionable&&(e=e.where("status","!=","done").where(y`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(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);
37
+ )`)),r?.sql&&(e=e.where(y`(${y.raw(r.sql)})`)),r?.sort??"priority"){case"due":e=e.orderBy(y`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(y`title COLLATE NOCASE`,"asc").orderBy("created_at","asc");break;case"created":e=e.orderBy("created_at","asc");break;default:e=e.orderBy(y`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(r?.limit!==void 0||r?.offset!==void 0)&&(e=e.limit(r.limit??-1)),r?.offset!==void 0&&(e=e.offset(r.offset)),(await e.execute()).map(B)}function ce(t,r){return r.all||r.status!==void 0?t:t.filter(e=>e.status!=="done")}async function p(t,r){let e=await t.selectFrom("tasks").selectAll().where("id","=",r).where("deleted_at","is",null).executeTakeFirst();if(e)return B(e);if(!r)return null;let s=r.replace(/[\\%_]/g,"\\$&"),n=await t.selectFrom("tasks").selectAll().where(y`id LIKE ${s} || '%' ESCAPE '\\'`).where("deleted_at","is",null).execute();if(n.length===1)return B(n[0]);if(n.length>1)throw new P(r,n.map(i=>i.id));return null}async function D(t,r,e,s){let n=await p(t,r);if(!n)return null;let o={updated_at:s??new Date().toISOString()};return e.title!==void 0&&(o.title=e.title),e.status!==void 0&&(o.status=e.status),e.priority!==void 0&&(o.priority=e.priority),e.owner!==void 0&&(o.owner=e.owner),e.due_at!==void 0&&(o.due_at=e.due_at),e.due_tz!==void 0&&(o.due_tz=e.due_tz),e.recurrence!==void 0&&(o.recurrence=e.recurrence),e.blocked_by!==void 0&&(o.blocked_by=JSON.stringify(e.blocked_by)),e.labels!==void 0&&(o.labels=JSON.stringify(e.labels)),e.metadata!==void 0&&(o.metadata=JSON.stringify(e.metadata)),await t.updateTable("tasks").set(o).where("id","=",n.id).execute(),p(t,n.id)}async function v(t,r,e,s){let n=await p(t,r);if(!n)return null;let i=e.trim();if(i.length===0||n.notes.some(c=>c.trim()===i))return n;let o=[...n.notes,i],a=s??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(o),updated_at:a}).where("id","=",n.id).execute(),p(t,n.id)}async function I(t,r,e,s){let n=await p(t,r);if(!n)return null;let i=s??new Date().toISOString();return await t.updateTable("tasks").set({notes:JSON.stringify(e),updated_at:i}).where("id","=",n.id).execute(),p(t,n.id)}async function ue(t,r,e){let s=await p(t,r);if(!s)return!1;let n=e??new Date().toISOString(),i=await t.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 m(t,r,e){let s=k(),n=e??new Date().toISOString();return await t.insertInto("oplog").values({id:s,task_id:r.task_id,device_id:r.device_id,op_type:r.op_type,field:r.field,value:r.value,timestamp:n}).execute(),{id:s,task_id:r.task_id,device_id:r.device_id,op_type:r.op_type,field:r.field,value:r.value,timestamp:n}}function Xe(){return"NO_COLOR"in process.env?!1:"FORCE_COLOR"in process.env?!0:process.stdout.isTTY??!1}function N(t,r){let e=`\x1B[${t}m`,s=`\x1B[${r}m`;return n=>Xe()?`${e}${n}${s}`:n}var ze=N(1,22),Je=N(2,22),Ke=N(3,23),He=N(37,39);function de(t,r){let s=new Intl.DateTimeFormat("en-US",{timeZone:r,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(new Date(t)),n=f=>Number(s.find(Q=>Q.type===f).value),i=n("year"),o=n("month")-1,a=n("day"),c=n("hour")===24?0:n("hour"),d=n("minute"),l=n("second");return(Date.UTC(i,o,a,c,d,l)-t)/6e4}function W(t,r){let e=Number(t.slice(0,4)),s=Number(t.slice(5,7))-1,n=Number(t.slice(8,10)),i=Number(t.slice(11,13))||0,o=Number(t.slice(14,16))||0,a=Number(t.slice(17,19))||0,c=Date.UTC(e,s,n,i,o,a),d=de(c,r),l=c-d*6e4,b=de(l,r);return c-b*6e4}function O(t){let r=Number(t.slice(0,4)),e=Number(t.slice(5,7))-1,s=Number(t.slice(8,10)),n=Number(t.slice(11,13))||0,i=Number(t.slice(14,16))||0,o=Number(t.slice(17,19))||0;return new Date(Date.UTC(r,e,s,n,i,o))}function pe(t){let r=t.getUTCFullYear(),e=String(t.getUTCMonth()+1).padStart(2,"0"),s=String(t.getUTCDate()).padStart(2,"0"),n=String(t.getUTCHours()).padStart(2,"0"),i=String(t.getUTCMinutes()).padStart(2,"0"),o=String(t.getUTCSeconds()).padStart(2,"0");return`${r}-${e}-${s}T${n}:${i}:${o}`}function j(t,r,e){let s=r??new Date;if(e){let i=W(t,e);return(t.slice(11,16)==="00:00"||!t.includes("T"))&&(i+=1440*60*1e3),i<s.getTime()}let n=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"&&n.setDate(n.getDate()+1),n<s}function ge(t,r,e){if(j(t,r,e))return!1;let s=r??new Date;if(e){let o=W(t,e);return(t.slice(11,16)==="00:00"||!t.includes("T"))&&(o+=1440*60*1e3),(o-s.getTime())/(1e3*60*60)<=48}let n=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"&&n.setDate(n.getDate()+1),(n.getTime()-s.getTime())/(1e3*60*60)<=48}var Ve={SU:0,MO:1,TU:2,WE:3,TH:4,FR:5,SA:6};function qe(t){let r=t.split(";"),e="",s=1,n=null,i=null;for(let o of r){let[a,c]=o.split("=");switch(a){case"FREQ":e=c;break;case"INTERVAL":s=Number(c);break;case"BYDAY":n=c.split(",");break;case"BYMONTHDAY":i=Number(c);break}}return{freq:e,interval:s,byDay:n,byMonthDay:i}}function L(t,r){let e=new Date(t);return e.setUTCDate(e.getUTCDate()+r),e}function X(t,r){let e=new Date(t),s=e.getUTCMonth()+r;return e.setUTCMonth(s),e.getUTCMonth()!==(s%12+12)%12&&e.setUTCDate(0),e}function me(t,r){let e=qe(t);switch(e.freq){case"DAILY":return L(r,e.interval);case"WEEKLY":{if(e.byDay&&e.byDay.length>0){let s=e.byDay.map(a=>Ve[a]).sort((a,c)=>a-c),n=r.getUTCDay();for(let a of s)if(a>n)return L(r,a-n);let i=7-n+s[0],o=(e.interval-1)*7;return L(r,i+o)}return L(r,e.interval*7)}case"MONTHLY":{if(e.byMonthDay!==null){let s=e.byMonthDay,n=new Date(r);if(r.getUTCDate()<s){if(n.setUTCDate(s),n.getUTCMonth()!==r.getUTCMonth()&&(n=new Date(Date.UTC(r.getUTCFullYear(),r.getUTCMonth()+1,0))),n.getTime()<=r.getTime()){n=X(r,e.interval);let i=n.getUTCMonth();n.setUTCDate(s),n.getUTCMonth()!==i&&(n=new Date(Date.UTC(n.getUTCFullYear(),i+1,0)))}}else{n=X(r,e.interval);let i=n.getUTCMonth();n.setUTCDate(s),n.getUTCMonth()!==i&&(n=new Date(Date.UTC(n.getUTCFullYear(),i+1,0)))}return n}return X(r,e.interval)}case"YEARLY":{let s=new Date(r);return s.setUTCFullYear(s.getUTCFullYear()+e.interval),s.getUTCMonth()!==r.getUTCMonth()&&s.setUTCDate(0),s}default:throw new Error(`Unsupported FREQ: ${e.freq}`)}}import{createHash as Ge}from"crypto";var Qe="oru-recurrence";function z(t){let r=Ge("sha256").update(`${Qe}:${t}`).digest();return F(r.subarray(0,8),11)}function J(t){return t===null?null:typeof t=="string"?t:JSON.stringify(t)}function fe(t){return{title:t.title,status:t.status,priority:t.priority,owner:t.owner,due_at:t.due_at,due_tz:t.due_tz,recurrence:t.recurrence,blocked_by:t.blocked_by,labels:t.labels,notes:t.notes,metadata:t.metadata}}var U=class{constructor(r,e){this.db=r;this.deviceId=e}async add(r){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n={...r,owner:r.owner||null},i=await Y(e,n,s);return await m(e,{task_id:i.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(fe(i))},s),i})}async _maybeSpawn(r,e,s){if(e.status!=="done"||!e.recurrence)return null;let n=z(e.id);if(await p(r,n))return null;let o=e.recurrence,a=o.startsWith("after:");a&&(o=o.slice(6));let c;if(a)if(e.due_tz){let Ie=new Intl.DateTimeFormat("en-US",{timeZone:e.due_tz,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(new Date(s)),_=Oe=>Number(Ie.find(Le=>Le.type===Oe).value),Ne=_("hour")===24?0:_("hour");c=new Date(Date.UTC(_("year"),_("month")-1,_("day"),Ne,_("minute"),_("second")))}else c=O(s.slice(0,19));else e.due_at?c=O(e.due_at):c=O(s.slice(0,19));let d=me(o,c),l=pe(d),b={id:n,title:e.title,priority:e.priority,owner:e.owner,due_at:l,due_tz:e.due_tz,recurrence:e.recurrence,labels:[...e.labels],metadata:{...e.metadata}},f=await Y(r,b,s);return await m(r,{task_id:f.id,device_id:this.deviceId,op_type:"create",field:null,value:JSON.stringify(fe(f))},s),f}async getSpawnedTask(r){let e=z(r);return p(this.db,e)}async validateBlockedBy(r,e){let s=null;if(r!==null){let i=await p(this.db,r);if(!i)return{valid:!1,error:`Task "${r}" not found.`};s=i.id}let n=[];for(let i of e){let o=await p(this.db,i);if(!o)return{valid:!1,error:`Task "${i}" not found.`};if(s!==null&&o.id===s)return{valid:!1,error:"A task cannot block itself."};n.push(o.id)}if(s!==null&&n.length>0){let i=await x(this.db),o=new Map(i.map(a=>[a.id,a]));for(let a of n){let c=[a],d=new Set;for(;c.length>0;){let l=c.shift();if(l===s)return{valid:!1,error:`Setting blocked_by to "${a}" would create a circular dependency.`};if(d.has(l))continue;d.add(l);let b=o.get(l);if(b)for(let f of b.blocked_by)d.has(f)||c.push(f)}}}return{valid:!0}}async list(r){return x(this.db,r)}async get(r){return p(this.db,r)}async update(r,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),i=await D(s,r,e,n);if(!i)return null;for(let[o,a]of Object.entries(e))o==="note"||a===void 0||await m(s,{task_id:i.id,device_id:this.deviceId,op_type:"update",field:o,value:J(a)},n);return await this._maybeSpawn(s,i,n),i})}async addNote(r,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),i=await p(s,r);if(!i)return null;let o=e.trim();if(o.length===0||i.notes.some(c=>c.trim()===o))return i;let a=await v(s,i.id,o,n);return await m(s,{task_id:i.id,device_id:this.deviceId,op_type:"update",field:"notes",value:o},n),a})}async updateWithNote(r,e,s){return this.db.transaction().execute(async n=>{let i=new Date().toISOString(),o=await D(n,r,e,i);if(!o)return null;let a=o.id;for(let[d,l]of Object.entries(e))d==="note"||l===void 0||await m(n,{task_id:a,device_id:this.deviceId,op_type:"update",field:d,value:J(l)},i);let c=s.trim();return c.length>0&&!o.notes.some(d=>d.trim()===c)&&(o=await v(n,a,c,i),await m(n,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes",value:c},i)),await this._maybeSpawn(n,o,i),o})}async clearNotes(r){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n=await I(e,r,[],s);return n?(await m(e,{task_id:n.id,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},s),n):null})}async clearNotesAndUpdate(r,e,s){return this.db.transaction().execute(async n=>{let i=new Date().toISOString(),o=await I(n,r,[],i);if(!o)return null;let a=o.id;if(await m(n,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},i),s){let d=s.trim();d.length>0&&(o=await v(n,a,d,i),await m(n,{task_id:a,device_id:this.deviceId,op_type:"update",field:"notes",value:d},i))}if(Object.keys(e).length>0){o=await D(n,a,e,i);for(let[d,l]of Object.entries(e))d==="note"||l===void 0||await m(n,{task_id:a,device_id:this.deviceId,op_type:"update",field:d,value:J(l)},i)}return await this._maybeSpawn(n,o,i),o})}async replaceNotes(r,e){return this.db.transaction().execute(async s=>{let n=new Date().toISOString(),i=await I(s,r,e,n);if(!i)return null;let o=i.id;await m(s,{task_id:o,device_id:this.deviceId,op_type:"update",field:"notes_clear",value:""},n);for(let a of e)await m(s,{task_id:o,device_id:this.deviceId,op_type:"update",field:"notes",value:a},n);return i})}async listLabels(){let r=await x(this.db),e=new Set;for(let s of r)for(let n of s.labels)e.add(n);return[...e].sort()}async getContext(r){let e=new Date,s=await this.list({sort:"priority",owner:r?.owner,label:r?.label}),n=await this.list({status:"done",sort:"priority",owner:r?.owner,label:r?.label}),i={overdue:[],due_soon:[],in_progress:[],actionable:[],blocked:[],recently_completed:[]},o=new Set(s.filter(l=>l.status!=="done").map(l=>l.id)),a=new Date(e.getTime()-1440*60*1e3).toISOString();for(let l of n)l.updated_at>=a&&i.recently_completed.push(l);for(let l of s){if(l.status==="done")continue;if(l.status==="in_progress"||l.status==="in_review"){i.in_progress.push(l);continue}if(l.due_at&&j(l.due_at,e,l.due_tz)){i.overdue.push(l);continue}if(l.due_at&&ge(l.due_at,e,l.due_tz)){i.due_soon.push(l);continue}if(l.blocked_by.some(f=>o.has(f))){i.blocked.push(l);continue}if(l.status==="todo"){i.actionable.push(l);continue}}let c=new Map;for(let l of[...s,...n])c.set(l.id,l.title);i.blockerTitles=c;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(r){let e=await p(this.db,r);return e?await this.db.selectFrom("oplog").selectAll().where("task_id","=",e.id).orderBy("timestamp","asc").orderBy(Ze`rowid`,"asc").execute():null}async delete(r){return this.db.transaction().execute(async e=>{let s=new Date().toISOString(),n=await p(e,r);if(!n)return!1;let i=await ue(e,n.id,s);return i&&await m(e,{task_id:n.id,device_id:this.deviceId,op_type:"delete",field:null,value:null},s),i})}};import{McpServer as nt}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as u}from"zod";function T(t){let{deleted_at:r,...e}=t;return e}import{z as g}from"zod";var et=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/,A=1e3,R=1e4,ye=200,be=100,Te=100,he=100,_e=50,ke=100,Se=5e3;var $=g.enum(S),M=g.enum(w),we=g.string().min(1,"Title is required.").max(A,`Title exceeds maximum length of ${A} characters.`),Ee=g.string().max(A,`Title exceeds maximum length of ${A} characters.`),K=g.array(g.string().max(ye,`Label exceeds maximum length of ${ye} characters.`)).max(be,`labels exceeds maximum of ${be} items.`),xe=g.array(g.string().max(R,`Note exceeds maximum length of ${R} characters.`)).max(he,`notes exceeds maximum of ${he} items.`),H=g.string().max(R,`Note exceeds maximum length of ${R} characters.`),V=g.array(g.string()).max(Te,`blocked_by exceeds maximum of ${Te} items.`),q=g.record(g.string(),g.unknown()).refine(t=>Object.keys(t).length<=_e,`Metadata exceeds maximum of ${_e} keys.`).refine(t=>Object.keys(t).every(r=>r.length<=ke),`Metadata key exceeds maximum length of ${ke} characters.`).refine(t=>Object.values(t).every(r=>typeof r!="string"||r.length<=Se),`Metadata value exceeds maximum length of ${Se} characters.`),tt=new Set(Intl.supportedValuesOf("timeZone"));function rt(t){return tt.has(t)}var cr=g.string().refine(t=>rt(t),"Invalid IANA timezone.").nullable().optional(),ur=g.string().regex(et,"Invalid date format. Expected YYYY-MM-DD, YYYY-MM-DDTHH:MM, or YYYY-MM-DDTHH:MM:SS.").nullable().optional();var De="0.0.6";function st(t){return t instanceof Error&&"code"in t&&typeof t.code=="string"&&t.code.startsWith("SQLITE_")}function h(t){return st(t)||t instanceof TypeError?"An internal error occurred. Please try again.":t instanceof Error?t.message:"An internal error occurred. Please try again."}function ve(t){let r=De,e=new nt({name:"oru",version:r},{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:u.object({title:we.describe("Task title, e.g. 'Fix login bug'"),id:u.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:$.optional().describe("Initial status. Valid values: todo, in_progress, in_review, done. Defaults to 'todo'."),priority:M.optional().describe("Priority level. Valid values: low, medium, high, urgent. Defaults to 'medium'."),owner:u.string().optional().describe("Assign to owner, e.g. 'alice'"),due_at:u.string().optional().describe("Due date as ISO 8601 datetime string, e.g. '2026-03-01T00:00:00.000Z'"),due_tz:u.string().nullable().optional().describe("IANA timezone for the due date, e.g. 'America/New_York'. When set, due_at is wall-clock time in this timezone. When null (default), due_at is floating local time."),blocked_by:V.optional().describe("Array of task IDs that must be completed before this task, e.g. ['0196b8e0-...']"),labels:K.optional().describe("Array of string labels to attach, e.g. ['bug', 'frontend']"),notes:xe.optional().describe("Initial notes to add to the task, e.g. ['Started migration']"),recurrence:u.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:q.optional().describe("Arbitrary JSON object for storing custom key-value data, e.g. {pr: 42}")})},async s=>{try{let n=await t.add(s);return{content:[{type:"text",text:JSON.stringify(T(n),null,2)}]}}catch(n){let i=n instanceof Error?n.message:String(n);if(s.id&&i.includes("UNIQUE constraint")){let o=await t.get(s.id);if(o)return{content:[{type:"text",text:JSON.stringify(T(o),null,2)}]}}return{content:[{type:"text",text:h(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:u.object({id:u.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID"),title:Ee.optional().describe("New title"),status:$.optional().describe("New status. Valid values: todo, in_progress, in_review, done."),priority:M.optional().describe("New priority. Valid values: low, medium, high, urgent."),owner:u.string().nullable().optional().describe("New owner. Set to null to unassign."),due_at:u.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."),due_tz:u.string().nullable().optional().describe("IANA timezone for the due date, e.g. 'America/New_York'. Set to null to make floating (local time)."),blocked_by:V.optional().describe("Array of task IDs that block this task. Replaces the existing list."),labels:K.optional().describe("Array of string labels. Replaces the existing list, e.g. ['bug', 'frontend']."),recurrence:u.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:q.optional().describe("Arbitrary JSON object. Merged with existing metadata."),note:H.optional().describe("A note to append to the task. Append-only - existing notes are not affected.")})},async s=>{try{let{id:n,note:i,...o}=s;if(o.metadata!==void 0){let c=await t.get(n);o.metadata={...c?.metadata??{},...o.metadata}}let a=i?await t.updateWithNote(n,o,i):await t.update(n,o);return a?{content:[{type:"text",text:JSON.stringify(T(a),null,2)}]}:{content:[{type:"text",text:`Task not found: ${n}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:h(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:u.object({id:u.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID")})},async({id:s})=>{try{return await t.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:h(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:u.object({status:$.optional().describe("Filter by status. Valid values: todo, in_progress, in_review, done. Pass 'done' to see completed tasks."),priority:M.optional().describe("Filter by priority. Valid values: low, medium, high, urgent."),owner:u.string().optional().describe("Filter by owner, e.g. 'alice'"),label:u.string().optional().describe("Filter by label, e.g. 'bug'"),search:u.string().optional().describe("Substring search across task titles (case-insensitive), e.g. 'login'"),sort:u.enum(["priority","due","title","created"]).optional().describe("Sort order. Valid values: priority, due, title, created."),actionable:u.boolean().optional().describe("When true, returns only actionable tasks - those with status 'todo' that are not blocked by other incomplete tasks."),all:u.boolean().optional().describe("Include done tasks (ignored when status filter is set)"),limit:u.number().optional().describe("Maximum number of results to return"),offset:u.number().optional().describe("Number of results to skip (for pagination)")})},async s=>{try{let{all:n,...i}=s,o=await t.list(i);return o=ce(o,{all:n,status:i.status}),{content:[{type:"text",text:JSON.stringify(o.map(T),null,2)}]}}catch(n){return{content:[{type:"text",text:h(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:u.object({id:u.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID")})},async({id:s})=>{try{let n=await t.get(s);return n?{content:[{type:"text",text:JSON.stringify(T(n),null,2)}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(n){return{content:[{type:"text",text:h(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:u.object({owner:u.string().optional().describe("Scope to a specific owner, e.g. 'alice'"),label:u.string().optional().describe("Filter by label, e.g. 'backend'")})},async s=>{try{let{sections:n,summary:i}=await t.getContext({owner:s.owner,label:s.label}),o={summary:i};for(let[a,c]of Object.entries(n))a==="blockerTitles"?o[a]=c:o[a]=c.map(T);return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(n){return{content:[{type:"text",text:h(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:u.object({id:u.string().describe("Task ID or unique ID prefix, e.g. '0196b8e0' or full UUID"),note:H.describe("Note text to append, e.g. 'Blocked on API review'")})},async({id:s,note:n})=>{try{let i=await t.addNote(s,n);return i?{content:[{type:"text",text:JSON.stringify(T(i),null,2)}]}:{content:[{type:"text",text:`Task not found: ${s}.`}],isError:!0}}catch(i){return{content:[{type:"text",text:h(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:u.object({})},async()=>{try{let s=await t.listLabels();return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(s){return{content:[{type:"text",text:h(s)}],isError:!0}}}),e}var G=ee();re(G);var ot=ne(G),at=ie(G),lt=new U(ot,at),ct=ve(lt),ut=new it;await ct.connect(ut);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tchayen/oru",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "oru - agent-friendly todo CLI with offline sync",
5
5
  "keywords": [
6
6
  "agent",