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