@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 +94 -87
- package/dist/mcp/index.js +4 -4
- package/package.json +9 -11
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`),{...
|
|
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
|
-
`,
|
|
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
|
|
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
|
-
`),
|
|
46
|
-
Update available: ${
|
|
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
|
|
49
|
-
`)){let o=n.indexOf("=");o!==-1&&(e[n.slice(0,o).trim()]=n.slice(o+1).trim())}return e}catch{return null}}function
|
|
50
|
-
`),
|
|
51
|
-
`);let s=new AbortController,a=setTimeout(()=>s.abort(),6e4),
|
|
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
|
-
`;
|
|
56
|
-
`)}async function
|
|
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;
|
|
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(
|
|
63
|
-
`)}function
|
|
64
|
-
`)}function
|
|
65
|
-
${
|
|
66
|
-
`)}function
|
|
67
|
-
`)}function
|
|
68
|
-
|
|
69
|
-
`)}function
|
|
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
|
|
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
|
-
`),
|
|
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{
|
|
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:
|
|
113
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`);for(let r of e)
|
|
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,
|
|
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;
|
|
129
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(`pull_cursor_${this.deviceId}`,s.cursor);let a=s.entries.filter(
|
|
130
|
-
`;if(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
|
|
138
|
-
`).filter(
|
|
139
|
-
`)),
|
|
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="${
|
|
153
|
-
local priority_values="${
|
|
154
|
-
local sort_values="${
|
|
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
|
-
`}
|
|
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=(${
|
|
300
|
+
status_values=(${I.join(" ")})
|
|
300
301
|
|
|
301
302
|
local -a priority_values
|
|
302
|
-
priority_values=(${
|
|
303
|
+
priority_values=(${R.join(" ")})
|
|
303
304
|
|
|
304
305
|
local -a sort_values
|
|
305
|
-
sort_values=(${
|
|
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
|
-
`}
|
|
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 '${
|
|
562
|
-
complete -c oru -n '__oru_using_subcommand filter add' -s p -l priority -a '${
|
|
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 '${
|
|
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 '${
|
|
602
|
-
complete -c oru -n '__oru_using_command list' -s s -l status -a '${
|
|
603
|
-
complete -c oru -n '__oru_using_command update' -s s -l status -a '${
|
|
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 '${
|
|
607
|
-
complete -c oru -n '__oru_using_command list' -s p -l priority -a '${
|
|
608
|
-
complete -c oru -n '__oru_using_command update' -s p -l priority -a '${
|
|
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 '${
|
|
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
|
|
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
|
-
`:"";
|
|
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
|
|
678
|
-
Restart your shell or run: source ~/${
|
|
679
|
-
Completions will be loaded automatically on next shell start.`}import
|
|
680
|
-
`),{}}}function
|
|
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
|
|
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,
|
|
686
|
-
`)}var
|
|
687
|
-
`),n,o=r=>process.stderr.write(r)){let r=n??
|
|
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(`${
|
|
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
|
|
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
|
|
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(
|
|
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"
|
|
711
|
-
${
|
|
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(
|
|
715
|
-
`);return}
|
|
716
|
-
`).filter(
|
|
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(
|
|
720
|
-
${
|
|
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`),
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
740
|
-
`),f}});let
|
|
741
|
-
`))}),
|
|
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((
|
|
746
|
-
Use: oru completions bash|zsh|fish`),process.exitCode=1;return}if(!process.stdin.isTTY){e(`Detected shell: ${
|
|
747
|
-
Run interactively: oru completions ${
|
|
748
|
-
`),process.exitCode=1,f()})})}),p.command("_complete <type> [prefix]",{hidden:!0}).action(async(
|
|
749
|
-
`))}),
|
|
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
|
|
3
|
-
`),
|
|
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
|
-
`),
|
|
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.
|
|
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
|
+
}
|