heyio 0.17.1 → 0.17.2

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.
@@ -44,7 +44,11 @@ export async function startApiServer() {
44
44
  app.use((_req, res, next) => {
45
45
  res.setHeader("Access-Control-Allow-Origin", "*");
46
46
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
47
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
47
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
48
+ if (_req.method === "OPTIONS") {
49
+ res.sendStatus(204);
50
+ return;
51
+ }
48
52
  next();
49
53
  });
50
54
  // Build API router
@@ -114,11 +118,56 @@ export async function startApiServer() {
114
118
  res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
115
119
  }
116
120
  });
117
- // Apply auth middleware to all subsequent routes
118
- api.use(requireAuth);
121
+ // Status endpoint public (non-sensitive version/uptime info)
119
122
  api.get("/status", (_req, res) => {
120
123
  res.json({ version: IO_VERSION, uptime: process.uptime() });
121
124
  });
125
+ // Notifications read endpoint — public (local-only app, nav badge needs data without auth race)
126
+ api.get("/notifications", (_req, res) => {
127
+ try {
128
+ const unreadOnly = _req.query.unread === "true";
129
+ const rows = unreadOnly
130
+ ? listUnreadNotifications()
131
+ : (() => {
132
+ const rawLimit = _req.query.limit;
133
+ const parsed = typeof rawLimit === "string" ? Number.parseInt(rawLimit, 10) : NaN;
134
+ const limit = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 200) : 50;
135
+ return listRecentNotifications(limit);
136
+ })();
137
+ const unreadCount = countUnreadNotifications();
138
+ const notifications = rows.map(({ id, title, text, created_at, read_at, source_type, source_ref }) => {
139
+ let source = { type: source_type };
140
+ if (source_ref) {
141
+ try {
142
+ const parsed = JSON.parse(source_ref);
143
+ source = { type: source_type, ...parsed };
144
+ }
145
+ catch {
146
+ // source_ref is not valid JSON — fall back to type-only
147
+ }
148
+ }
149
+ return { id, title, text, created_at, read_at, source };
150
+ });
151
+ res.json({ notifications, unreadCount });
152
+ }
153
+ catch (e) {
154
+ console.error("Error listing notifications:", e);
155
+ res.status(500).json({ error: "Failed to list notifications" });
156
+ }
157
+ });
158
+ // SSE events — public (AppNav subscribes on load before auth resolves)
159
+ api.get("/events", (req, res) => {
160
+ res.setHeader("Content-Type", "text/event-stream");
161
+ res.setHeader("Cache-Control", "no-cache");
162
+ res.setHeader("Connection", "keep-alive");
163
+ res.flushHeaders();
164
+ sseConnections.add(res);
165
+ req.on("close", () => {
166
+ sseConnections.delete(res);
167
+ });
168
+ });
169
+ // Apply auth middleware to all subsequent routes
170
+ api.use(requireAuth);
122
171
  // Install a skill from pasted SKILL.md content (issue #117)
123
172
  api.post("/skills/paste", (req, res) => {
124
173
  const { content: skillContent, slug } = req.body;
@@ -602,39 +651,6 @@ export async function startApiServer() {
602
651
  res.status(500).json({ error: (e instanceof Error ? e.message : String(e)) });
603
652
  }
604
653
  });
605
- // Notifications endpoints
606
- api.get("/notifications", (_req, res) => {
607
- try {
608
- const unreadOnly = _req.query.unread === "true";
609
- const rows = unreadOnly
610
- ? listUnreadNotifications()
611
- : (() => {
612
- const rawLimit = _req.query.limit;
613
- const parsed = typeof rawLimit === "string" ? Number.parseInt(rawLimit, 10) : NaN;
614
- const limit = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 200) : 50;
615
- return listRecentNotifications(limit);
616
- })();
617
- const unreadCount = countUnreadNotifications();
618
- const notifications = rows.map(({ id, title, text, created_at, read_at, source_type, source_ref }) => {
619
- let source = { type: source_type };
620
- if (source_ref) {
621
- try {
622
- const parsed = JSON.parse(source_ref);
623
- source = { type: source_type, ...parsed };
624
- }
625
- catch {
626
- // source_ref is not valid JSON — fall back to type-only
627
- }
628
- }
629
- return { id, title, text, created_at, read_at, source };
630
- });
631
- res.json({ notifications, unreadCount });
632
- }
633
- catch (e) {
634
- console.error("Error listing notifications:", e);
635
- res.status(500).json({ error: "Failed to list notifications" });
636
- }
637
- });
638
654
  api.post("/notifications/read-all", (_req, res) => {
639
655
  try {
640
656
  const marked = markAllNotificationsRead();
@@ -690,16 +706,6 @@ export async function startApiServer() {
690
706
  });
691
707
  res.json({ response: fullResponse });
692
708
  });
693
- api.get("/events", (req, res) => {
694
- res.setHeader("Content-Type", "text/event-stream");
695
- res.setHeader("Cache-Control", "no-cache");
696
- res.setHeader("Connection", "keep-alive");
697
- res.flushHeaders();
698
- sseConnections.add(res);
699
- req.on("close", () => {
700
- sseConnections.delete(res);
701
- });
702
- });
703
709
  // Wiki endpoints (issue #105)
704
710
  function extractWikiTitle(pageContent, fallback) {
705
711
  const match = pageContent.match(/^#\s+(.+)/m);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyio",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "IO — a personal AI assistant built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "io": "dist/index.js"
@@ -1 +1 @@
1
- import{d as _,o as L,c as a,a as n,t as i,b as m,e as f,f as M,F as j,r as C,g as u,h as p,i as o,w as E,u as S,j as T}from"./index-BGjTyM3L.js";const D={class:"flex flex-col h-full p-6"},B={class:"flex justify-between items-center mb-6"},V={key:0,class:"text-[10px] font-mono text-accent bg-accent/10 border border-accent/20 px-2 py-0.5 rounded-full"},H={key:0,class:"flex-1 flex items-center justify-center"},N={key:1,class:"flex-1 flex flex-col items-center justify-center"},z={key:2,class:"space-y-2 overflow-y-auto flex-1 pr-1"},F=["onClick"],$={class:"flex-1 min-w-0"},I={class:"flex items-start justify-between gap-2"},P={class:"text-sm font-semibold text-txt-primary leading-snug"},Z={class:"text-[10px] text-txt-muted font-mono shrink-0 mt-0.5"},A={key:0,class:"text-xs text-txt-secondary mt-1 line-clamp-2 leading-relaxed"},W=["onClick","disabled"],q={key:0,class:"px-4 pb-4"},G=["innerHTML"],J={key:3,class:"mt-3 flex items-center gap-2 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-xl px-3.5 py-2.5"},Q=_({__name:"InboxView",setup(K){const l=u([]),v=u(!0),r=u(new Set),d=u(new Set),c=u(null);function h(t){try{const e=t.includes("T")||t.endsWith("Z")?t:t.replace(" ","T")+"Z";return new Date(e).toLocaleString()}catch{return t}}function g(t){return t.replace(/[#*`_~[\]()]/g,"").slice(0,200)}function b(t){const e=new Set(r.value);e.has(t)?e.delete(t):e.add(t),r.value=e}async function w(){v.value=!0,c.value=null;try{const t=await p("/api/inbox");if(t.ok){const e=await t.json();l.value=e.entries??[]}}catch{}v.value=!1}async function k(t){if(!window.confirm("Delete this inbox entry?"))return;const e=new Set(d.value);e.add(t),d.value=e;try{const s=await p(`/api/inbox/${t}`,{method:"DELETE"});if(s.ok){l.value=l.value.filter(y=>y.id!==t);const x=new Set(r.value);x.delete(t),r.value=x}else c.value=`Failed to delete entry (HTTP ${s.status})`}catch(s){c.value=s instanceof Error?s.message:"Delete failed"}finally{const s=new Set(d.value);s.delete(t),d.value=s}}return L(w),(t,e)=>(o(),a("div",D,[n("div",B,[e[0]||(e[0]=n("div",null,[n("h2",{class:"text-xl font-semibold text-txt-primary tracking-tight"},"Inbox"),n("p",{class:"text-xs text-txt-muted mt-0.5"},"Messages & incoming items")],-1)),l.value.length>0?(o(),a("span",V,i(l.value.length)+" item"+i(l.value.length===1?"":"s"),1)):m("",!0)]),v.value?(o(),a("div",H,[...e[1]||(e[1]=[n("div",{class:"flex items-center gap-3 text-txt-muted text-sm"},[n("div",{class:"w-1.5 h-1.5 rounded-full bg-accent animate-pulse"}),f(" Loading… ")],-1)])])):l.value.length===0?(o(),a("div",N,[...e[2]||(e[2]=[M('<div class="w-14 h-14 rounded-xl bg-surface-2 border border-edge flex items-center justify-center mb-4"><svg class="w-7 h-7 text-txt-muted" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 012.012 1.244l.256.512a2.25 2.25 0 002.013 1.244h3.218a2.25 2.25 0 002.013-1.244l.256-.512a2.25 2.25 0 012.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 00-2.15-1.588H6.911a2.25 2.25 0 00-2.15 1.588L2.35 13.177a2.25 2.25 0 00-.1.661z"></path></svg></div><p class="text-txt-muted text-sm font-medium">Inbox is empty</p><p class="text-txt-muted/60 text-xs mt-1">No messages right now</p>',3)])])):(o(),a("ul",z,[(o(!0),a(j,null,C(l.value,s=>(o(),a("li",{key:s.id,class:"group bg-surface-2/50 border border-edge rounded-xl hover:border-edge-bright hover:shadow-card transition-all duration-200 overflow-hidden animate-fade-in"},[n("div",{class:"flex items-start gap-3 p-4 cursor-pointer",onClick:x=>b(s.id)},[e[4]||(e[4]=n("div",{class:"mt-1.5 w-1.5 h-1.5 rounded-full bg-accent shadow-glow-sm shrink-0"},null,-1)),n("div",$,[n("div",I,[n("span",P,i(s.title),1),n("span",Z,i(h(s.created_at)),1)]),r.value.has(s.id)?m("",!0):(o(),a("p",A,i(g(s.body)),1))]),n("button",{onClick:E(x=>k(s.id),["stop"]),disabled:d.value.has(s.id),class:"opacity-0 group-hover:opacity-100 shrink-0 p-1.5 rounded-lg text-txt-muted hover:text-red-400 hover:bg-red-500/10 border border-transparent hover:border-red-500/20 disabled:opacity-30 transition-all duration-200",title:"Delete entry"},[...e[3]||(e[3]=[n("svg",{class:"w-3.5 h-3.5",fill:"none",viewBox:"0 0 24 24","stroke-width":"2",stroke:"currentColor"},[n("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"})],-1)])],8,W)],8,F),r.value.has(s.id)?(o(),a("div",q,[n("div",{class:"text-sm text-txt-secondary bg-surface-0/60 rounded-xl p-4 border border-edge/50 wiki-content leading-relaxed",innerHTML:S(T)(s.body)},null,8,G)])):m("",!0)]))),128))])),c.value?(o(),a("div",J,[e[5]||(e[5]=n("svg",{class:"w-4 h-4 shrink-0",viewBox:"0 0 20 20",fill:"currentColor"},[n("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z","clip-rule":"evenodd"})],-1)),f(" "+i(c.value),1)])):m("",!0)]))}});export{Q as default};
1
+ import{d as _,o as L,c as a,a as n,t as i,b as m,e as f,f as M,F as j,r as C,g as u,h as p,i as o,w as E,u as S,j as T}from"./index-Bh0Ukzjz.js";const D={class:"flex flex-col h-full p-6"},B={class:"flex justify-between items-center mb-6"},V={key:0,class:"text-[10px] font-mono text-accent bg-accent/10 border border-accent/20 px-2 py-0.5 rounded-full"},H={key:0,class:"flex-1 flex items-center justify-center"},N={key:1,class:"flex-1 flex flex-col items-center justify-center"},z={key:2,class:"space-y-2 overflow-y-auto flex-1 pr-1"},F=["onClick"],$={class:"flex-1 min-w-0"},I={class:"flex items-start justify-between gap-2"},P={class:"text-sm font-semibold text-txt-primary leading-snug"},Z={class:"text-[10px] text-txt-muted font-mono shrink-0 mt-0.5"},A={key:0,class:"text-xs text-txt-secondary mt-1 line-clamp-2 leading-relaxed"},W=["onClick","disabled"],q={key:0,class:"px-4 pb-4"},G=["innerHTML"],J={key:3,class:"mt-3 flex items-center gap-2 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-xl px-3.5 py-2.5"},Q=_({__name:"InboxView",setup(K){const l=u([]),v=u(!0),r=u(new Set),d=u(new Set),c=u(null);function h(t){try{const e=t.includes("T")||t.endsWith("Z")?t:t.replace(" ","T")+"Z";return new Date(e).toLocaleString()}catch{return t}}function g(t){return t.replace(/[#*`_~[\]()]/g,"").slice(0,200)}function b(t){const e=new Set(r.value);e.has(t)?e.delete(t):e.add(t),r.value=e}async function w(){v.value=!0,c.value=null;try{const t=await p("/api/inbox");if(t.ok){const e=await t.json();l.value=e.entries??[]}}catch{}v.value=!1}async function k(t){if(!window.confirm("Delete this inbox entry?"))return;const e=new Set(d.value);e.add(t),d.value=e;try{const s=await p(`/api/inbox/${t}`,{method:"DELETE"});if(s.ok){l.value=l.value.filter(y=>y.id!==t);const x=new Set(r.value);x.delete(t),r.value=x}else c.value=`Failed to delete entry (HTTP ${s.status})`}catch(s){c.value=s instanceof Error?s.message:"Delete failed"}finally{const s=new Set(d.value);s.delete(t),d.value=s}}return L(w),(t,e)=>(o(),a("div",D,[n("div",B,[e[0]||(e[0]=n("div",null,[n("h2",{class:"text-xl font-semibold text-txt-primary tracking-tight"},"Inbox"),n("p",{class:"text-xs text-txt-muted mt-0.5"},"Messages & incoming items")],-1)),l.value.length>0?(o(),a("span",V,i(l.value.length)+" item"+i(l.value.length===1?"":"s"),1)):m("",!0)]),v.value?(o(),a("div",H,[...e[1]||(e[1]=[n("div",{class:"flex items-center gap-3 text-txt-muted text-sm"},[n("div",{class:"w-1.5 h-1.5 rounded-full bg-accent animate-pulse"}),f(" Loading… ")],-1)])])):l.value.length===0?(o(),a("div",N,[...e[2]||(e[2]=[M('<div class="w-14 h-14 rounded-xl bg-surface-2 border border-edge flex items-center justify-center mb-4"><svg class="w-7 h-7 text-txt-muted" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 012.012 1.244l.256.512a2.25 2.25 0 002.013 1.244h3.218a2.25 2.25 0 002.013-1.244l.256-.512a2.25 2.25 0 012.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 00-2.15-1.588H6.911a2.25 2.25 0 00-2.15 1.588L2.35 13.177a2.25 2.25 0 00-.1.661z"></path></svg></div><p class="text-txt-muted text-sm font-medium">Inbox is empty</p><p class="text-txt-muted/60 text-xs mt-1">No messages right now</p>',3)])])):(o(),a("ul",z,[(o(!0),a(j,null,C(l.value,s=>(o(),a("li",{key:s.id,class:"group bg-surface-2/50 border border-edge rounded-xl hover:border-edge-bright hover:shadow-card transition-all duration-200 overflow-hidden animate-fade-in"},[n("div",{class:"flex items-start gap-3 p-4 cursor-pointer",onClick:x=>b(s.id)},[e[4]||(e[4]=n("div",{class:"mt-1.5 w-1.5 h-1.5 rounded-full bg-accent shadow-glow-sm shrink-0"},null,-1)),n("div",$,[n("div",I,[n("span",P,i(s.title),1),n("span",Z,i(h(s.created_at)),1)]),r.value.has(s.id)?m("",!0):(o(),a("p",A,i(g(s.body)),1))]),n("button",{onClick:E(x=>k(s.id),["stop"]),disabled:d.value.has(s.id),class:"opacity-0 group-hover:opacity-100 shrink-0 p-1.5 rounded-lg text-txt-muted hover:text-red-400 hover:bg-red-500/10 border border-transparent hover:border-red-500/20 disabled:opacity-30 transition-all duration-200",title:"Delete entry"},[...e[3]||(e[3]=[n("svg",{class:"w-3.5 h-3.5",fill:"none",viewBox:"0 0 24 24","stroke-width":"2",stroke:"currentColor"},[n("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"})],-1)])],8,W)],8,F),r.value.has(s.id)?(o(),a("div",q,[n("div",{class:"text-sm text-txt-secondary bg-surface-0/60 rounded-xl p-4 border border-edge/50 wiki-content leading-relaxed",innerHTML:S(T)(s.body)},null,8,G)])):m("",!0)]))),128))])),c.value?(o(),a("div",J,[e[5]||(e[5]=n("svg",{class:"w-4 h-4 shrink-0",viewBox:"0 0 20 20",fill:"currentColor"},[n("path",{"fill-rule":"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z","clip-rule":"evenodd"})],-1)),f(" "+i(c.value),1)])):m("",!0)]))}});export{Q as default};