idea-manager 2.3.1 → 2.4.0
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/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/advisor-actions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/archive/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/filesystem/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/filesystem/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/global-advisor/route.js +1 -1
- package/.next/server/app/api/global-advisor/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/global-memo/route.js +2 -2
- package/.next/server/app/api/global-memo/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/maintenance/route.js +2 -2
- package/.next/server/app/api/maintenance/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/advisor/route.js +1 -1
- package/.next/server/app/api/projects/[id]/advisor/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/apply-distribute/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/auto-distribute/route.js +2 -2
- package/.next/server/app/api/projects/[id]/auto-distribute/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/brainstorm/route.js +2 -2
- package/.next/server/app/api/projects/[id]/brainstorm/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/git-sync/route.js +2 -2
- package/.next/server/app/api/projects/[id]/git-sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/route.js +2 -2
- package/.next/server/app/api/projects/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route.js +2 -2
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route.js +2 -2
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route.js +2 -2
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/reorder/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/route.js +2 -2
- package/.next/server/app/api/projects/[id]/sub-projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/route.js +2 -2
- package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/search/route.js +2 -2
- package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sync/route.js +2 -2
- package/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/move/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +13 -13
- package/.next/server/chunks/697.js +2 -2
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +2 -2
- package/.next/static/chunks/app/{page-a0491c7ecdd773ca.js → page-835e6af14011d7a8.js} +1 -1
- package/bin/postinstall.js +27 -7
- package/package.json +1 -1
- package/src/cli.ts +43 -10
- package/src/components/ui/FileTreeDrawer.tsx +1 -1
- package/src/lib/ai/client.ts +21 -7
- package/src/lib/auto-update.ts +22 -3
- package/src/lib/db/index.ts +29 -5
- package/src/lib/scheduler.ts +9 -3
- package/src/lib/sync/git.ts +17 -14
- package/src/lib/watcher.ts +4 -4
- /package/.next/static/{cNwwQZS0Jhwz2yyh3ks6E → 8qtkIvEkJ-lmfHkJP6hlo}/_buildManifest.js +0 -0
- /package/.next/static/{cNwwQZS0Jhwz2yyh3ks6E → 8qtkIvEkJ-lmfHkJP6hlo}/_ssgManifest.js +0 -0
|
@@ -22,7 +22,7 @@ ${t}
|
|
|
22
22
|
- 기술 스택: React + TypeScript + Vite (monorepo)
|
|
23
23
|
- DB: PostgreSQL (jabis 스키마)
|
|
24
24
|
- 한국어로 응답할 것
|
|
25
|
-
- 코드 제안 시 기존 컨벤션을 따를 것`,className:"w-full bg-input border border-border rounded-lg px-4 py-3 text-sm text-foreground resize-none focus:border-primary focus:outline-none leading-relaxed font-mono min-h-[300px]"})}),(0,s.jsxs)("div",{className:"flex items-center justify-between px-5 py-3 border-t border-border",children:[(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:"Cmd+Enter to save"}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("button",{onClick:o,className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground border border-border rounded-md transition-colors",children:"Cancel"}),(0,s.jsx)("button",{onClick:()=>r(a),className:"px-3 py-1.5 text-xs bg-primary text-white rounded-md hover:bg-primary-hover transition-colors",children:"Save"})]})]})]})]}):null}let eK={success:{icon:"✅",color:"text-success"},error:{icon:"❌",color:"text-destructive"},"no-git":{icon:"➖",color:"text-muted-foreground"},"no-path":{icon:"➖",color:"text-muted-foreground"}};function ez({open:e,results:t,onClose:r}){let o=(0,n.useRef)(null);if((0,n.useEffect)(()=>{if(!e)return;let t=e=>{"Escape"===e.key&&r()};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,r]),!e)return null;let a=t.filter(e=>"success"===e.status).length,l=t.filter(e=>"error"===e.status).length,i=t.filter(e=>"no-git"===e.status||"no-path"===e.status).length;return(0,s.jsx)("div",{ref:o,onClick:e=>{e.target===o.current&&r()},className:"fixed inset-0 z-50 flex items-center justify-center",style:{background:"rgba(0,0,0,0.5)",backdropFilter:"blur(2px)"},children:(0,s.jsxs)("div",{className:"bg-card border border-border rounded-xl shadow-2xl shadow-black/40 w-full max-w-md mx-4 animate-dialog-in",children:[(0,s.jsxs)("div",{className:"p-5 border-b border-border",children:[(0,s.jsx)("h3",{className:"text-sm font-semibold text-foreground",children:"Git Sync Results"}),(0,s.jsxs)("p",{className:"text-xs text-muted-foreground mt-1",children:[a," synced, ",l," failed, ",i," skipped"]})]}),(0,s.jsx)("div",{className:"max-h-64 overflow-y-auto p-3 space-y-1.5",children:0===t.length?(0,s.jsx)("p",{className:"text-xs text-muted-foreground text-center py-4",children:"No projects with linked folders"}):t.map(e=>{let t=eK[e.status];return(0,s.jsxs)("div",{className:"flex items-start gap-2 p-2 rounded-lg bg-muted/50",children:[(0,s.jsx)("span",{className:"text-sm flex-shrink-0",children:t.icon}),(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("div",{className:"text-xs font-medium truncate",children:e.projectName}),(0,s.jsx)("div",{className:`text-xs ${t.color} truncate`,title:e.message,children:e.message})]})]},e.projectId)})}),(0,s.jsx)("div",{className:"flex justify-end px-5 pb-4 pt-2",children:(0,s.jsx)("button",{onClick:r,className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground bg-muted hover:bg-card-hover border border-border rounded-md transition-colors",children:"Close"})})]})})}let eJ={ts:"TS",tsx:"TX",js:"JS",jsx:"JX",json:"{}",md:"MD",css:"CS",scss:"SC",html:"HT",svg:"SV",png:"PN",jpg:"JP",py:"PY",go:"GO",rs:"RS",java:"JA",sql:"SQ",sh:"SH",yml:"YM",yaml:"YM",toml:"TM",xml:"XM",txt:"TX",env:"EN",lock:"LK",gitignore:"GI"};function eB({rootPath:e,onClose:t}){let[r,o]=(0,n.useState)({}),[a,l]=(0,n.useState)(new Set([e])),i=(0,n.useRef)(null),d=(0,n.useCallback)(async e=>{o(t=>({...t,[e]:{entries:[],loaded:!1,loading:!0}}));try{let t=await fetch(`/api/filesystem/tree?path=${encodeURIComponent(e)}`);if(!t.ok)throw Error("Failed to load");let r=await t.json();o(t=>({...t,[e]:{entries:r.entries,loaded:!0,loading:!1}}))}catch{o(t=>({...t,[e]:{entries:[],loaded:!0,loading:!1,error:"Failed to load"}}))}},[]);(0,n.useEffect)(()=>{d(e)},[e,d]),(0,n.useEffect)(()=>{let e=e=>{"Escape"===e.key&&t()};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[t]);let c=(e,t)=>{let n=r[e];return n?n.loading?(0,s.jsx)("div",{className:"flex items-center gap-2 py-1",style:{paddingLeft:16*t+12},children:(0,s.jsx)("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Loading..."})}):n.error?(0,s.jsx)("div",{className:"flex items-center gap-2 py-1",style:{paddingLeft:16*t+12},children:(0,s.jsx)("span",{className:"text-xs text-destructive",children:n.error})}):0===n.entries.length?(0,s.jsx)("div",{className:"flex items-center gap-2 py-1",style:{paddingLeft:16*t+12},children:(0,s.jsx)("span",{className:"text-xs text-muted-foreground italic",children:"Empty"})}):n.entries.map(e=>{var n,o;let i="directory"===e.type,u=a.has(e.path);return(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:`flex items-center gap-1.5 py-[3px] pr-3 cursor-pointer transition-colors hover:bg-card-hover group ${i?"text-foreground":"text-muted-foreground"}`,style:{paddingLeft:16*t+12},onClick:()=>{var t;return i&&(t=e.path,void l(e=>{let s=new Set(e);return s.has(t)?s.delete(t):(s.add(t),r[t]?.loaded||r[t]?.loading||d(t)),s}))},children:i?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"w-4 text-center text-xs text-muted-foreground flex-shrink-0",children:u?"▼":"▶"}),(0,s.jsx)("span",{className:"text-sm flex-shrink-0",children:u?"\uD83D\uDCC2":"\uD83D\uDCC1"}),(0,s.jsx)("span",{className:"text-sm truncate flex-1 font-medium",children:e.name})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"w-4 flex-shrink-0"}),(0,s.jsx)("span",{className:"text-[10px] font-mono w-5 text-center flex-shrink-0 text-muted-foreground/70",children:(n=e.extension)?eJ[n]||n.slice(0,2).toUpperCase():"--"}),(0,s.jsx)("span",{className:"text-sm truncate flex-1",children:e.name}),void 0!==e.size&&(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/50 tabular-nums flex-shrink-0",children:(o=e.size)<1024?`${o}B`:o<1048576?`${(o/1024).toFixed(0)}K`:`${(o/1048576).toFixed(1)}M`})]})}),i&&u&&c(e.path,t+1)]},e.path)}):null},u=e.split(
|
|
25
|
+
- 코드 제안 시 기존 컨벤션을 따를 것`,className:"w-full bg-input border border-border rounded-lg px-4 py-3 text-sm text-foreground resize-none focus:border-primary focus:outline-none leading-relaxed font-mono min-h-[300px]"})}),(0,s.jsxs)("div",{className:"flex items-center justify-between px-5 py-3 border-t border-border",children:[(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:"Cmd+Enter to save"}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("button",{onClick:o,className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground border border-border rounded-md transition-colors",children:"Cancel"}),(0,s.jsx)("button",{onClick:()=>r(a),className:"px-3 py-1.5 text-xs bg-primary text-white rounded-md hover:bg-primary-hover transition-colors",children:"Save"})]})]})]})]}):null}let eK={success:{icon:"✅",color:"text-success"},error:{icon:"❌",color:"text-destructive"},"no-git":{icon:"➖",color:"text-muted-foreground"},"no-path":{icon:"➖",color:"text-muted-foreground"}};function ez({open:e,results:t,onClose:r}){let o=(0,n.useRef)(null);if((0,n.useEffect)(()=>{if(!e)return;let t=e=>{"Escape"===e.key&&r()};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,r]),!e)return null;let a=t.filter(e=>"success"===e.status).length,l=t.filter(e=>"error"===e.status).length,i=t.filter(e=>"no-git"===e.status||"no-path"===e.status).length;return(0,s.jsx)("div",{ref:o,onClick:e=>{e.target===o.current&&r()},className:"fixed inset-0 z-50 flex items-center justify-center",style:{background:"rgba(0,0,0,0.5)",backdropFilter:"blur(2px)"},children:(0,s.jsxs)("div",{className:"bg-card border border-border rounded-xl shadow-2xl shadow-black/40 w-full max-w-md mx-4 animate-dialog-in",children:[(0,s.jsxs)("div",{className:"p-5 border-b border-border",children:[(0,s.jsx)("h3",{className:"text-sm font-semibold text-foreground",children:"Git Sync Results"}),(0,s.jsxs)("p",{className:"text-xs text-muted-foreground mt-1",children:[a," synced, ",l," failed, ",i," skipped"]})]}),(0,s.jsx)("div",{className:"max-h-64 overflow-y-auto p-3 space-y-1.5",children:0===t.length?(0,s.jsx)("p",{className:"text-xs text-muted-foreground text-center py-4",children:"No projects with linked folders"}):t.map(e=>{let t=eK[e.status];return(0,s.jsxs)("div",{className:"flex items-start gap-2 p-2 rounded-lg bg-muted/50",children:[(0,s.jsx)("span",{className:"text-sm flex-shrink-0",children:t.icon}),(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("div",{className:"text-xs font-medium truncate",children:e.projectName}),(0,s.jsx)("div",{className:`text-xs ${t.color} truncate`,title:e.message,children:e.message})]})]},e.projectId)})}),(0,s.jsx)("div",{className:"flex justify-end px-5 pb-4 pt-2",children:(0,s.jsx)("button",{onClick:r,className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground bg-muted hover:bg-card-hover border border-border rounded-md transition-colors",children:"Close"})})]})})}let eJ={ts:"TS",tsx:"TX",js:"JS",jsx:"JX",json:"{}",md:"MD",css:"CS",scss:"SC",html:"HT",svg:"SV",png:"PN",jpg:"JP",py:"PY",go:"GO",rs:"RS",java:"JA",sql:"SQ",sh:"SH",yml:"YM",yaml:"YM",toml:"TM",xml:"XM",txt:"TX",env:"EN",lock:"LK",gitignore:"GI"};function eB({rootPath:e,onClose:t}){let[r,o]=(0,n.useState)({}),[a,l]=(0,n.useState)(new Set([e])),i=(0,n.useRef)(null),d=(0,n.useCallback)(async e=>{o(t=>({...t,[e]:{entries:[],loaded:!1,loading:!0}}));try{let t=await fetch(`/api/filesystem/tree?path=${encodeURIComponent(e)}`);if(!t.ok)throw Error("Failed to load");let r=await t.json();o(t=>({...t,[e]:{entries:r.entries,loaded:!0,loading:!1}}))}catch{o(t=>({...t,[e]:{entries:[],loaded:!0,loading:!1,error:"Failed to load"}}))}},[]);(0,n.useEffect)(()=>{d(e)},[e,d]),(0,n.useEffect)(()=>{let e=e=>{"Escape"===e.key&&t()};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[t]);let c=(e,t)=>{let n=r[e];return n?n.loading?(0,s.jsx)("div",{className:"flex items-center gap-2 py-1",style:{paddingLeft:16*t+12},children:(0,s.jsx)("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Loading..."})}):n.error?(0,s.jsx)("div",{className:"flex items-center gap-2 py-1",style:{paddingLeft:16*t+12},children:(0,s.jsx)("span",{className:"text-xs text-destructive",children:n.error})}):0===n.entries.length?(0,s.jsx)("div",{className:"flex items-center gap-2 py-1",style:{paddingLeft:16*t+12},children:(0,s.jsx)("span",{className:"text-xs text-muted-foreground italic",children:"Empty"})}):n.entries.map(e=>{var n,o;let i="directory"===e.type,u=a.has(e.path);return(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:`flex items-center gap-1.5 py-[3px] pr-3 cursor-pointer transition-colors hover:bg-card-hover group ${i?"text-foreground":"text-muted-foreground"}`,style:{paddingLeft:16*t+12},onClick:()=>{var t;return i&&(t=e.path,void l(e=>{let s=new Set(e);return s.has(t)?s.delete(t):(s.add(t),r[t]?.loaded||r[t]?.loading||d(t)),s}))},children:i?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"w-4 text-center text-xs text-muted-foreground flex-shrink-0",children:u?"▼":"▶"}),(0,s.jsx)("span",{className:"text-sm flex-shrink-0",children:u?"\uD83D\uDCC2":"\uD83D\uDCC1"}),(0,s.jsx)("span",{className:"text-sm truncate flex-1 font-medium",children:e.name})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"w-4 flex-shrink-0"}),(0,s.jsx)("span",{className:"text-[10px] font-mono w-5 text-center flex-shrink-0 text-muted-foreground/70",children:(n=e.extension)?eJ[n]||n.slice(0,2).toUpperCase():"--"}),(0,s.jsx)("span",{className:"text-sm truncate flex-1",children:e.name}),void 0!==e.size&&(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/50 tabular-nums flex-shrink-0",children:(o=e.size)<1024?`${o}B`:o<1048576?`${(o/1024).toFixed(0)}K`:`${(o/1048576).toFixed(1)}M`})]})}),i&&u&&c(e.path,t+1)]},e.path)}):null},u=e.split(/[\\/]/).filter(Boolean).pop()||e;return(0,s.jsxs)("div",{ref:i,className:"fixed inset-0 z-50 flex justify-end",onClick:e=>{e.target===i.current&&t()},children:[(0,s.jsx)("div",{className:"absolute inset-0 bg-black/40 backdrop-blur-[2px]"}),(0,s.jsxs)("div",{className:"relative w-[420px] max-w-[85vw] h-full bg-card border-l border-border shadow-2xl flex flex-col animate-drawer-in",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between px-4 py-3 border-b border-border flex-shrink-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 min-w-0",children:[(0,s.jsx)("span",{className:"text-base",children:"\uD83D\uDCC2"}),(0,s.jsxs)("div",{className:"min-w-0",children:[(0,s.jsx)("h2",{className:"text-sm font-semibold truncate",children:u}),(0,s.jsx)("p",{className:"text-[10px] text-muted-foreground font-mono truncate",children:e})]})]}),(0,s.jsx)("button",{onClick:t,className:"text-muted-foreground hover:text-foreground transition-colors text-lg px-1",title:"Close (ESC)",children:"\xd7"})]}),(0,s.jsx)("div",{className:"flex-1 overflow-y-auto py-2",children:c(e,0)}),(0,s.jsx)("div",{className:"px-4 py-2 border-t border-border flex-shrink-0",children:(0,s.jsx)("p",{className:"text-[10px] text-muted-foreground",children:"ESC to close"})})]})]})}function eW({open:e,projectId:t,onClose:r,onApplied:o}){let[a,l]=(0,n.useState)(!1),[i,d]=(0,n.useState)(!1),[c,u]=(0,n.useState)(null),[x,m]=(0,n.useState)([]),[p,h]=(0,n.useState)(new Set);(0,n.useEffect)(()=>{e&&(m([]),u(null),h(new Set),f())},[e]),(0,n.useEffect)(()=>{if(!e)return;let t=e=>{"Escape"===e.key&&r()};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,r]);let f=async()=>{l(!0),u(null);try{let e=await fetch(`/api/projects/${t}/auto-distribute`,{method:"POST"}),r=await e.json();if(!e.ok){let e=r.raw?`
|
|
26
26
|
|
|
27
27
|
AI 응답:
|
|
28
28
|
${r.raw}`:"";u((r.error||"Failed to get distribution")+e);return}m(r.distributions||[])}catch{u("AI 호출에 실패했습니다.")}finally{l(!1)}},g=async()=>{let e=x.filter(e=>e.tasks.length>0);if(0!==e.length){d(!0);try{let s=await fetch(`/api/projects/${t}/apply-distribute`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({distributions:e})});if(s.ok)o(),r();else{let e=await s.json();u(e.error||"Failed to apply")}}catch{u("적용에 실패했습니다.")}finally{d(!1)}}},b=x.reduce((e,t)=>e+t.tasks.length,0);return e?(0,s.jsxs)("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[(0,s.jsx)("div",{className:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:r}),(0,s.jsxs)("div",{className:"relative bg-card border border-border rounded-xl shadow-2xl w-[720px] max-h-[85vh] flex flex-col animate-dialog-in",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between px-5 py-3 border-b border-border",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("h3",{className:"text-sm font-semibold",children:"Auto Distribute"}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground mt-0.5",children:"AI가 브레인스토밍을 분석하여 태스크를 분배합니다"})]}),(0,s.jsx)("button",{onClick:r,className:"text-muted-foreground hover:text-foreground text-lg px-1",children:"x"})]}),(0,s.jsxs)("div",{className:"flex-1 overflow-y-auto p-4",children:[a&&(0,s.jsxs)("div",{className:"flex flex-col items-center justify-center py-16 gap-3",children:[(0,s.jsx)("div",{className:"w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin"}),(0,s.jsx)("p",{className:"text-sm text-muted-foreground",children:"AI가 분석 중..."})]}),c&&(0,s.jsxs)("div",{className:"bg-danger/10 border border-danger/30 rounded-lg p-3 mb-3",children:[(0,s.jsx)("pre",{className:"text-xs text-danger whitespace-pre-wrap break-all max-h-[200px] overflow-y-auto",children:c}),(0,s.jsx)("button",{onClick:f,className:"text-xs text-accent hover:underline mt-1",children:"다시 시도"})]}),!a&&x.length>0&&(0,s.jsx)("div",{className:"space-y-3",children:x.map((e,t)=>(0,s.jsxs)("div",{className:"border border-border rounded-lg overflow-hidden",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 px-3 py-2 bg-muted/50",children:[(0,s.jsx)("button",{onClick:()=>{h(e=>{let r=new Set(e);return r.has(t)?r.delete(t):r.add(t),r})},className:"text-xs text-muted-foreground hover:text-foreground w-4",children:p.has(t)?"▶":"▼"}),(0,s.jsx)("span",{className:`text-[10px] px-1.5 py-0.5 rounded font-medium ${e.is_new?"bg-success/15 text-success":"bg-accent/15 text-accent"}`,children:e.is_new?"NEW":"EXISTING"}),(0,s.jsx)("input",{value:e.sub_project_name,onChange:e=>{var r;return r=e.target.value,void m(e=>e.map((e,s)=>s===t?{...e,sub_project_name:r}:e))},className:"flex-1 bg-transparent text-sm font-medium text-foreground focus:outline-none border-b border-transparent focus:border-primary"}),(0,s.jsx)("span",{className:"text-xs text-muted-foreground tabular-nums",children:e.tasks.length}),(0,s.jsx)("button",{onClick:()=>{m(e=>e.filter((e,r)=>r!==t))},className:"text-xs text-muted-foreground hover:text-danger px-1",title:"Remove group",children:"x"})]}),!p.has(t)&&(0,s.jsxs)("div",{className:"divide-y divide-border",children:[e.tasks.map((e,r)=>{var n;return(0,s.jsxs)("div",{className:"flex items-center gap-2 px-3 py-1.5 group hover:bg-muted/30",children:[(n=e.priority,(0,s.jsx)("span",{className:`inline-block w-2 h-2 rounded-full ${{high:"bg-danger",medium:"bg-warning",low:"bg-muted-foreground/40"}[n]}`})),(0,s.jsx)("input",{value:e.title,onChange:e=>{var s;return s=e.target.value,void m(e=>e.map((e,n)=>n===t?{...e,tasks:e.tasks.map((e,t)=>t===r?{...e,title:s}:e)}:e))},className:"flex-1 bg-transparent text-xs text-foreground focus:outline-none border-b border-transparent focus:border-primary"}),(0,s.jsxs)("select",{value:e.priority,onChange:e=>{var s;return s=e.target.value,void m(e=>e.map((e,n)=>n===t?{...e,tasks:e.tasks.map((e,t)=>t===r?{...e,priority:s}:e)}:e))},className:"text-[10px] bg-transparent text-muted-foreground cursor-pointer hover:text-foreground opacity-0 group-hover:opacity-100 transition-opacity",children:[(0,s.jsx)("option",{value:"high",children:"high"}),(0,s.jsx)("option",{value:"medium",children:"medium"}),(0,s.jsx)("option",{value:"low",children:"low"})]}),x.length>1&&(0,s.jsxs)("select",{value:"",onChange:e=>{let s=parseInt(e.target.value);isNaN(s)||m(e=>{let n=e[t].tasks[r];return e.map((e,o)=>o===t?{...e,tasks:e.tasks.filter((e,t)=>t!==r)}:o===s?{...e,tasks:[...e.tasks,n]}:e)})},className:"text-[10px] bg-transparent text-muted-foreground cursor-pointer hover:text-foreground opacity-0 group-hover:opacity-100 transition-opacity",title:"Move to...",children:[(0,s.jsx)("option",{value:"",children:"Move"}),x.map((e,r)=>r!==t&&(0,s.jsx)("option",{value:r,children:e.sub_project_name},r))]}),(0,s.jsx)("button",{onClick:()=>{m(e=>e.map((e,s)=>s===t?{...e,tasks:e.tasks.filter((e,t)=>t!==r)}:e))},className:"text-xs text-muted-foreground hover:text-danger opacity-0 group-hover:opacity-100 transition-opacity px-0.5",children:"x"})]},r)}),0===e.tasks.length&&(0,s.jsx)("div",{className:"px-3 py-2 text-xs text-muted-foreground italic",children:"No tasks (this group will be skipped)"})]})]},t))}),!a&&!c&&0===x.length&&(0,s.jsx)("div",{className:"text-center py-16 text-sm text-muted-foreground",children:"No distribution available"})]}),(0,s.jsxs)("div",{className:"flex items-center justify-between px-5 py-3 border-t border-border",children:[(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:x.length>0&&`${x.length} projects, ${b} tasks`}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[!a&&x.length>0&&(0,s.jsx)("button",{onClick:f,className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground border border-border rounded-md transition-colors",children:"Retry"}),(0,s.jsx)("button",{onClick:r,className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground border border-border rounded-md transition-colors",children:"Cancel"}),(0,s.jsx)("button",{onClick:g,disabled:i||a||0===b,className:"px-4 py-1.5 text-xs bg-primary text-white rounded-md hover:bg-primary-hover transition-colors disabled:opacity-50",children:i?"Applying...":`Apply (${b})`})]})]})]})]}):null}let eU=/```action\s*\n([\s\S]*?)```/g;function eG(e){return e&&"object"==typeof e?"create_task"===e.type?"string"!=typeof e.subProjectId||"string"!=typeof e.title?null:{type:"create_task",subProjectId:e.subProjectId,projectId:"string"==typeof e.projectId?e.projectId:void 0,title:e.title,description:"string"==typeof e.description?e.description:void 0,priority:["high","medium","low"].includes(e.priority)?e.priority:void 0,status:"string"==typeof e.status?e.status:void 0}:"update_task"===e.type?"string"==typeof e.taskId&&e.changes&&"object"==typeof e.changes?{type:"update_task",taskId:e.taskId,changes:e.changes}:null:null:null}function eH({action:e}){return(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-success text-xs font-bold",children:"+"}),(0,s.jsx)("span",{className:"text-xs text-foreground truncate flex-1",children:e.title}),e.priority&&"medium"!==e.priority&&(0,s.jsx)("span",{className:`text-[10px] px-1 rounded ${"high"===e.priority?"bg-destructive/20 text-destructive":"bg-muted text-muted-foreground"}`,children:e.priority})]})}function eq({action:e}){let t=Object.entries(e.changes).filter(([,e])=>void 0!==e).map(([e,t])=>`${e}: ${t}`).join(", ");return(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-warning text-xs font-bold",children:"~"}),(0,s.jsxs)("span",{className:"text-xs text-muted-foreground truncate",children:["task:",e.taskId.slice(0,8)]}),(0,s.jsxs)("span",{className:"text-xs text-foreground truncate flex-1",children:["→ ",t]})]})}function eV({actions:e,onApplied:t}){let[r,o]=(0,n.useState)("pending"),[a,l]=(0,n.useState)(null),[i,d]=(0,n.useState)(null),c=(0,n.useCallback)(async()=>{o("applying"),l(null);try{let r=await fetch("/api/advisor-actions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({actions:e})}),s=await r.json();if(!r.ok)throw Error(s.error||`HTTP ${r.status}`);let n=s.results,a=n.filter(e=>e.success).length,l=n.filter(e=>!e.success).length;l>0?(d(`${a} 성공, ${l} 실패`),o(a>0?"applied":"error")):(d(`${a}개 적용 완료`),o("applied")),t?.(),window.dispatchEvent(new Event("advisor-action-applied"))}catch(e){l(e instanceof Error?e.message:"적용 실패"),o("error")}},[e,t]),u=e.filter(e=>"create_task"===e.type).length,x=e.filter(e=>"update_task"===e.type).length;return(0,s.jsxs)("div",{className:`my-2 border rounded-lg text-xs ${"applied"===r?"border-success/30 bg-success/5":"error"===r?"border-destructive/30 bg-destructive/5":"border-border bg-card"}`,children:[(0,s.jsxs)("div",{className:"px-3 py-2 border-b border-border flex items-center justify-between",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 text-muted-foreground",children:[(0,s.jsx)("span",{children:"Proposed Actions"}),u>0&&(0,s.jsxs)("span",{className:"text-success",children:["+",u," create"]}),x>0&&(0,s.jsxs)("span",{className:"text-warning",children:["~",x," update"]})]}),"applied"===r&&(0,s.jsxs)("span",{className:"text-success",children:["✓ ",i]}),"error"===r&&(0,s.jsxs)("span",{className:"text-destructive",children:["⚠ ",a||i]})]}),(0,s.jsx)("div",{className:"px-3 py-2 space-y-1.5 max-h-[200px] overflow-y-auto",children:e.map((e,t)=>(0,s.jsx)("div",{children:"create_task"===e.type?(0,s.jsx)(eH,{action:e}):(0,s.jsx)(eq,{action:e})},t))}),"applied"!==r&&(0,s.jsxs)("div",{className:"px-3 py-2 border-t border-border flex justify-end gap-2",children:["error"===r&&(0,s.jsx)("button",{onClick:c,className:"px-2 py-1 text-foreground hover:text-primary transition-colors",children:"재시도"}),"pending"===r&&(0,s.jsx)("button",{onClick:c,className:"px-3 py-1 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity",children:"적용"}),"applying"===r&&(0,s.jsxs)("span",{className:"flex items-center gap-1.5 text-muted-foreground",children:[(0,s.jsx)("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-warning animate-pulse"}),"적용 중…"]})]})]})}function eX({basePath:e,title:t,shortcutHint:r,placeholder:o,emptyIcon:a,emptyHints:l,activityType:i,activityLabel:d,onClose:c}){let[u,x]=(0,n.useState)([]),[m,p]=(0,n.useState)(""),[h,f]=(0,n.useState)(!1),g=(0,n.useRef)(null),b=(0,n.useRef)(null),j=(0,n.useCallback)(async()=>{try{let t=await fetch(e);if(!t.ok)return;let r=await t.json();Array.isArray(r)&&x(r)}catch{}},[e]);(0,n.useEffect)(()=>{j()},[j]),(0,n.useEffect)(()=>{g.current?.scrollIntoView({behavior:"smooth"})},[u]),(0,n.useEffect)(()=>{b.current?.focus()},[]),(0,n.useEffect)(()=>{let e=e=>{"Escape"===e.key&&(e.preventDefault(),c())};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[c]);let y=(0,n.useCallback)(async()=>{let t=m.trim();if(!t||h)return;p(""),f(!0);let r=`${i}-${Date.now()}`;E({id:r,type:i,label:d,startedAt:Date.now()});let s=`temp-${Date.now()}`;x(e=>[...e,{id:s,project_id:"",role:"user",content:t,created_at:new Date().toISOString()}]);try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:t})});if(r.ok){let e=await r.json();x(t=>[...t.filter(e=>e.id!==s),e.userMessage,e.aiMessage])}}catch{}T(r),f(!1),b.current?.focus()},[m,h,e,i,d]),v=(0,n.useCallback)(async()=>{await fetch(e,{method:"DELETE"}),x([]),b.current?.focus()},[e]);return(0,s.jsxs)("div",{className:"fixed inset-0 z-[55] flex items-center justify-center",onClick:c,children:[(0,s.jsx)("div",{className:"absolute inset-0 bg-black/40",style:{backdropFilter:"blur(2px)"}}),(0,s.jsxs)("div",{onClick:e=>e.stopPropagation(),className:"relative w-[560px] max-w-[90vw] h-[80vh] max-h-[700px] bg-card border border-border rounded-xl shadow-2xl flex flex-col animate-dialog-in",children:[(0,s.jsxs)("div",{className:"px-4 py-3 border-b border-border flex items-center justify-between flex-shrink-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-sm font-semibold text-foreground",children:t}),(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/60",children:r})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[u.length>0&&(0,s.jsx)("button",{onClick:v,className:"text-xs text-muted-foreground hover:text-foreground transition-colors px-1",children:"Clear"}),(0,s.jsx)("button",{onClick:c,className:"text-muted-foreground hover:text-foreground transition-colors text-lg leading-none",children:"\xd7"})]})]}),(0,s.jsxs)("div",{className:"flex-1 overflow-y-auto px-4 py-3 space-y-3 min-h-0",children:[0===u.length&&!h&&(0,s.jsxs)("div",{className:"flex flex-col items-center justify-center h-full text-center gap-3 text-muted-foreground",children:[(0,s.jsx)("div",{className:"text-2xl",children:a}),l.map((e,t)=>(0,s.jsx)("div",{className:0===t?"text-sm":"text-xs text-muted-foreground/70 max-w-[300px] leading-relaxed",children:e},t))]}),u.filter(e=>"system"!==e.role).map(e=>(0,s.jsx)("div",{className:`flex flex-col ${"user"===e.role?"items-end":"items-start"}`,children:"user"===e.role?(0,s.jsx)("div",{className:"max-w-[92%] px-3 py-2 rounded-lg text-sm leading-relaxed bg-accent text-white rounded-br-sm whitespace-pre-wrap",children:e.content}):(0,s.jsx)("div",{className:"max-w-[92%] w-full",children:(function(e){let t=[],r=0;for(let s of e.matchAll(eU)){let n=e.slice(r,s.index);n.trim()&&t.push({type:"markdown",text:n});try{let e=JSON.parse(s[1]),r=(Array.isArray(e)?e:[e]).map(eG).filter(e=>null!==e);r.length>0?t.push({type:"actions",actions:r}):t.push({type:"markdown",text:s[0]})}catch{t.push({type:"markdown",text:s[0]})}r=(s.index??0)+s[0].length}let s=e.slice(r);return s.trim()&&t.push({type:"markdown",text:s}),{segments:t}})(e.content).segments.map((e,t)=>"markdown"===e.type?(0,s.jsx)("div",{className:"px-3 py-2 rounded-lg bg-muted text-foreground rounded-bl-sm chat-markdown text-sm leading-relaxed",children:(0,s.jsx)(eP.oz,{remarkPlugins:[eA.A],children:e.text})},t):(0,s.jsx)(eV,{actions:e.actions},t))})},e.id)),h&&(0,s.jsx)("div",{className:"flex items-start",children:(0,s.jsx)("div",{className:"px-3 py-2 rounded-lg bg-muted text-foreground rounded-bl-sm",children:(0,s.jsxs)("div",{className:"flex gap-1",children:[(0,s.jsx)("div",{className:"w-1.5 h-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:"0ms"}}),(0,s.jsx)("div",{className:"w-1.5 h-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:"150ms"}}),(0,s.jsx)("div",{className:"w-1.5 h-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:"300ms"}})]})})}),(0,s.jsx)("div",{ref:g})]}),(0,s.jsxs)("div",{className:"flex gap-1.5 px-3 py-3 border-t border-border flex-shrink-0",children:[(0,s.jsx)("textarea",{ref:b,value:m,onChange:e=>p(e.target.value),onKeyDown:e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),y())},placeholder:o,rows:2,className:"flex-1 bg-input border border-border rounded-md px-3 py-2 text-sm text-foreground resize-none focus:border-primary focus:outline-none"}),(0,s.jsx)("button",{onClick:y,disabled:!m.trim()||h,className:"px-3 py-2 bg-accent text-white text-sm rounded-md disabled:opacity-40 hover:bg-accent/80 transition-colors flex-shrink-0 self-end",children:"Send"})]})]})]})}function eZ({projectId:e,projectName:t,onClose:r}){return(0,s.jsx)(eX,{basePath:`/api/projects/${e}/advisor`,title:"Project Advisor",shortcutHint:"⌘L",placeholder:"프로젝트에 대해 무엇이든 물어보세요…",emptyIcon:"\uD83E\uDDED",emptyHints:["프로젝트 전체 맥락을 보고 답합니다",'"다음 뭐부터 하면 좋겠어?"\n"빠진 작업 없나?"\n"이번 주 진행 상황 정리해줘"'],activityType:"project-advisor",activityLabel:t?`Advisor: ${t}`:"Project Advisor",onClose:r})}function eQ({id:e,initialSubId:t,initialTaskId:r}){let{state:o,setActiveTab:a,consumeInitial:l,updateTabName:i}=x(),d=o.activeTabId===e,c=(0,n.useRef)(t),u=(0,n.useRef)(r);(0,n.useEffect)(()=>{t&&(c.current=t),r&&(u.current=r),(t||r)&&(t&&b(t),r&&N(r),l(e))},[t,r]);let[m,p]=(0,n.useState)(null),[h,f]=(0,n.useState)([]),[g,b]=(0,n.useState)(null),[j,y]=(0,n.useState)([]),[v,N]=(0,n.useState)(null),[k,w]=(0,n.useState)(!1),[C,S]=(0,n.useState)(null),[E,T]=(0,n.useState)(!1),[I,$]=(0,n.useState)(!0),[P,A]=(0,n.useState)(""),[_,L]=(0,n.useState)(!1),[O,R]=(0,n.useState)(!1),[F,K]=(0,n.useState)(!1),[z,J]=(0,n.useState)(!1),[B,W]=(0,n.useState)(null),[U,G]=(0,n.useState)(null),[H,q]=(0,n.useState)(!1),[V,X]=(0,n.useState)(!1),[Z,Q]=(0,n.useState)({}),[Y,ee]=(0,n.useState)(null),et=(0,n.useRef)(!1),[er,es]=(0,n.useState)(500),[en,eo]=(0,n.useState)(500),ea=(0,n.useRef)(null),el=(0,n.useRef)(null),ei=(0,n.useRef)(0),ed=(0,n.useRef)(0),ec=(0,n.useCallback)((e,t)=>{t.preventDefault(),el.current=e,ei.current=t.clientX,ed.current="left"===e?er:en},[er,en]);(0,n.useEffect)(()=>{let e=e=>{if(!el.current)return;let t=e.clientX-ei.current,r=Math.max(180,Math.min(900,ed.current+t));"left"===el.current?es(r):eo(r)},t=()=>{el.current=null};return window.addEventListener("mousemove",e),window.addEventListener("mouseup",t),()=>{window.removeEventListener("mousemove",e),window.removeEventListener("mouseup",t)}},[]),(0,n.useEffect)(()=>{fetch(`/api/projects/${e}`).then(e=>e.ok?e.json():null).then(t=>{t&&(p(t),i(e,t.name))})},[e,i]);let eu=(0,n.useCallback)(async()=>{let t=await fetch(`/api/projects/${e}/sub-projects`);if(!t.ok)return;let r=await t.json();return f(r),r},[e]);(0,n.useEffect)(()=>{eu().then(e=>{if(!e||0===e.length)return;let t=c.current;t&&e.some(e=>e.id===t)?b(t):g||b(e[0].id)})},[eu]),(0,n.useEffect)(()=>{g?fetch(`/api/projects/${e}/sub-projects/${g}/tasks`).then(e=>e.json()).then(e=>{y(e);let t=u.current;t&&e.some(e=>e.id===t)&&(N(t),u.current=void 0)}):y([])},[e,g]);let ex=j.find(e=>e.id===v)??null;(0,n.useEffect)(()=>{let t=()=>{eu(),g&&fetch(`/api/projects/${e}/sub-projects/${g}/tasks`).then(e=>e.json()).then(y).catch(()=>{})};return window.addEventListener("advisor-action-applied",t),()=>window.removeEventListener("advisor-action-applied",t)},[e,g,eu]);let em=async()=>{if(!P.trim())return;let t=await fetch(`/api/projects/${e}/sub-projects`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:P.trim()})});if(t.ok){let e=await t.json();A(""),T(!1),await eu(),b(e.id)}},ep=async t=>{if(!g)return;let r=await fetch(`/api/projects/${e}/sub-projects/${g}/tasks`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t})});if(r.ok){let e=await r.json();y(t=>[...t,e]),N(e.id),eu()}},eh=async(t,r)=>{let s=await fetch(`/api/projects/${e}/sub-projects/${g}/tasks/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:r})});if(s.ok){let e=await s.json();y(r=>r.map(r=>r.id===t?e:r)),eu()}},ef=async(t,r)=>{let s=await fetch(`/api/projects/${e}/sub-projects/${g}/tasks/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({is_today:r})});if(s.ok){let e=await s.json();y(r=>r.map(r=>r.id===t?e:r))}},eg=async t=>{if(!v||!g)return;let r=await fetch(`/api/projects/${e}/sub-projects/${g}/tasks/${v}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(r.ok){let e=await r.json();y(t=>t.map(t=>t.id===v?e:t)),eu()}},ej=async t=>{f(e=>{let r=new Map(e.map(e=>[e.id,e]));return t.map(e=>r.get(e)).filter(Boolean)}),await fetch(`/api/projects/${e}/sub-projects`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({orderedIds:t})})},ey=async t=>{let r=t||v;if(!r||!g)return;let s=j.find(e=>e.id===r);if(!s)return;await fetch(`/api/projects/${e}/sub-projects/${g}/tasks/${r}?mode=archive`,{method:"DELETE"}),y(e=>e.filter(e=>e.id!==r)),v===r&&N(null),eu(),Y?.timer&&clearTimeout(Y.timer);let n=setTimeout(()=>ee(null),3e4);ee({taskId:r,title:s.title,timer:n})},ev=async()=>{if(Y){if(clearTimeout(Y.timer),await fetch(`/api/archive?action=restore&taskId=${Y.taskId}`,{method:"POST"}),g){let t=await fetch(`/api/projects/${e}/sub-projects/${g}/tasks`);t.ok&&y(await t.json())}eu(),ee(null)}},eN=async t=>{C&&("delete-sub"===C.type?(await fetch(`/api/projects/${e}/sub-projects/${C.id}`,{method:"DELETE"}),g===C.id&&(b(null),N(null)),eu()):"delete-task"===C.type&&(await fetch(`/api/projects/${e}/sub-projects/${g}/tasks/${C.id}?mode=${t||"archive"}`,{method:"DELETE"}),y(e=>e.filter(e=>e.id!==C.id)),v===C.id&&N(null),eu()),S(null))},ek=async t=>{let r=await fetch(`/api/projects/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({project_path:t})});r.ok&&(p(await r.json()),w(!1))},ew=async t=>{let r=await fetch(`/api/projects/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({ai_context:t})});r.ok&&(p(await r.json()),L(!1))},eC=(0,n.useCallback)(async(t=!1)=>{if(!et.current){et.current=!0,t||J(!0);try{let r=await fetch(`/api/projects/${e}/git-sync`,{method:"POST"});if(r.ok){let e=await r.json();G(new Date),t||W(e)}}catch{}finally{et.current=!1,t||J(!1)}}},[e]);(0,n.useEffect)(()=>{if(!m?.project_path)return;let e=setInterval(()=>eC(!0),18e5);return()=>clearInterval(e)},[m?.project_path,eC]);let eS=async()=>{if(!m)return;let t=await fetch(`/api/projects/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({watch_enabled:!m.watch_enabled})});t.ok&&p(await t.json())};return((0,n.useEffect)(()=>{let e=e=>{if(!d)return;let t=e.target,r=t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||(t?.isContentEditable??!1)||!!t?.closest?.(".cm-editor");if(!r&&"KeyB"===e.code&&!e.metaKey&&!e.ctrlKey){e.preventDefault(),$(e=>!e);return}if(!r&&"KeyN"===e.code&&!e.metaKey&&!e.ctrlKey){e.preventDefault(),T(!0);return}if(!r&&"KeyT"===e.code&&!e.metaKey&&!e.ctrlKey&&g){e.preventDefault();let t=document.querySelector("[data-add-task]");t?.click();return}if((e.metaKey||e.ctrlKey)&&"KeyL"===e.code){e.preventDefault(),R(e=>!e);return}if(v&&g&&!r){let t={Digit1:"idea",Digit2:"doing",Digit3:"done",Digit4:"problem"};(e.metaKey||e.ctrlKey)&&t[e.code]&&(e.preventDefault(),eh(v,t[e.code]))}};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)}),m)?(0,s.jsxs)("div",{className:"flex flex-col h-full",children:[(0,s.jsxs)("header",{className:"flex items-center justify-between px-4 py-2 border-b border-border bg-card flex-shrink-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)("button",{onClick:()=>a("dashboard"),className:"text-muted-foreground hover:text-foreground hover:bg-muted transition-colors text-sm px-2 py-1 rounded-md",children:"← Back"}),(0,s.jsx)("span",{className:"text-border",children:"|"}),(0,s.jsx)("h1",{className:"text-sm font-semibold",children:m.name}),m.project_path&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"text-xs text-muted-foreground font-mono truncate max-w-48",title:m.project_path,children:m.project_path}),(0,s.jsx)("button",{onClick:()=>q(!0),className:"text-xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors px-1.5 py-0.5 rounded",title:"View file tree",children:"\uD83D\uDCC2"})]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsxs)("select",{value:m.agent_type||"claude",onChange:async t=>{let r=await fetch(`/api/projects/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent_type:t.target.value})});r.ok&&p(await r.json())},className:"px-2 py-1.5 text-xs bg-muted border border-border rounded-md text-foreground cursor-pointer hover:bg-card-hover transition-colors",title:"AI Agent",children:[(0,s.jsx)("option",{value:"claude",children:"Claude"}),(0,s.jsx)("option",{value:"gemini",children:"Gemini"}),(0,s.jsx)("option",{value:"codex",children:"Codex"})]}),(0,s.jsxs)("button",{onClick:eS,className:`px-3 py-1.5 text-xs border rounded-md transition-colors flex items-center gap-1.5 ${m.watch_enabled?"bg-success/15 text-success border-success/30 hover:bg-success/25":"bg-muted hover:bg-card-hover text-muted-foreground border-border"}`,title:m.watch_enabled?"Watch ON":"Watch OFF",children:[(0,s.jsx)("span",{className:`inline-block w-2 h-2 rounded-full ${m.watch_enabled?"bg-success animate-pulse":"bg-muted-foreground/40"}`}),"Watch"]}),(0,s.jsxs)("button",{onClick:()=>L(!0),className:`px-3 py-1.5 text-xs border rounded-md transition-colors ${m.ai_context?"bg-accent/15 text-accent border-accent/30 hover:bg-accent/25":"bg-muted hover:bg-card-hover text-muted-foreground border-border"}`,children:["AI Policy",m.ai_context?" *":""]}),(0,s.jsx)("button",{onClick:()=>R(!0),className:`px-3 py-1.5 text-xs border rounded-md transition-colors ${O?"bg-primary/15 text-primary border-primary/30":"bg-muted hover:bg-card-hover text-muted-foreground border-border"}`,title:"프로젝트 어드바이저 (⌘L)",children:"Advisor"}),m.project_path?(0,s.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,s.jsxs)("button",{onClick:()=>eC(!1),disabled:z,className:"px-3 py-1.5 text-xs bg-muted hover:bg-card-hover text-foreground border border-border rounded-md transition-colors disabled:opacity-50 flex items-center gap-1.5",title:U?`Last sync: ${U.toLocaleTimeString()}`:"Git pull",children:[(0,s.jsx)("span",{className:z?"animate-spin":"",children:"↻"}),z?"Syncing...":"Git Sync"]}),U&&(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:U.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})})]}):(0,s.jsx)("button",{onClick:()=>w(!0),className:"px-3 py-1.5 text-xs bg-muted hover:bg-card-hover text-foreground border border-border rounded-md transition-colors",children:"Link folder"})]})]}),(0,s.jsxs)("div",{ref:ea,className:"flex-1 flex overflow-hidden",children:[I?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("div",{style:{width:er},className:"border-r border-border flex flex-col flex-shrink-0",children:(0,s.jsx)(eb,{projectId:e,onCollapse:()=>$(!1)})}),(0,s.jsx)("div",{className:"panel-resize-handle",onMouseDown:e=>ec("left",e),children:(0,s.jsx)("div",{className:"panel-resize-handle-bar"})})]}):(0,s.jsx)("button",{onClick:()=>$(!0),className:"w-8 border-r border-border flex-shrink-0 flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-card-hover transition-colors text-xs",title:"Show brainstorming (B)",style:{writingMode:"vertical-rl"},children:"Brainstorm"}),(0,s.jsxs)("div",{style:{width:en},className:"border-r border-border flex flex-col flex-shrink-0",children:[E&&(0,s.jsx)("div",{className:"px-3 py-2 border-b border-border",children:(0,s.jsx)("input",{type:"text",value:P,onChange:e=>A(e.target.value),onKeyDown:e=>{"Enter"===e.key&&em(),"Escape"===e.key&&(A(""),T(!1))},placeholder:"Project name...",className:"w-full bg-input border border-border rounded px-2 py-1 text-xs focus:border-primary focus:outline-none text-foreground",autoFocus:!0})}),(0,s.jsx)(eT,{subProjects:h,tasks:j,selectedSubId:g,selectedTaskId:v,onSelectSub:e=>{b(e),N(null)},onSelectTask:e=>{N(e),Q(t=>{if("done"!==t[e])return t;let r={...t};return delete r[e],r})},onCreateSub:()=>T(!0),onDeleteSub:e=>{S({type:"delete-sub",id:e})},onRenameSub:async(t,r)=>{(await fetch(`/api/projects/${e}/sub-projects/${t}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:r})})).ok&&eu()},onCreateTask:ep,onStatusChange:eh,onTodayToggle:ef,onDeleteTask:ey,onReorderSubs:ej,onReorderTasks:async t=>{g&&(y(e=>{let r=new Map(e.map(e=>[e.id,e]));return t.map(e=>r.get(e)).filter(Boolean)}),await fetch(`/api/projects/${e}/sub-projects/${g}/tasks/reorder`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({orderedIds:t})}))},onAutoDistribute:()=>X(!0),chatStates:Z})]}),(0,s.jsx)("div",{className:"panel-resize-handle",onMouseDown:e=>ec("center",e),children:(0,s.jsx)("div",{className:"panel-resize-handle-bar"})}),(0,s.jsx)("div",{className:"flex-1 min-w-0",children:ex?(0,s.jsx)(eR,{task:ex,projectId:e,subProjectId:g,siblingTasks:j,onUpdate:eg,onDelete:ey,focusMode:F,onFocusModeChange:K,onTaskPromoted:e=>y(t=>[...t,e]),onTaskMoved:()=>{y(e=>e.filter(e=>e.id!==v)),N(null)},onChatStateChange:(e,t)=>{Q(r=>({...r,[e]:t}))}}):(0,s.jsx)("div",{className:"flex items-center justify-center h-full text-muted-foreground text-sm",children:j.length>0?"Select a task":g?"Create a task to get started":"Select a project"})})]}),k&&(0,s.jsx)(D,{onSelect:ek,onCancel:()=>w(!1),initialPath:m.project_path||void 0}),(0,s.jsx)(M,{open:C?.type==="delete-sub",title:"Delete project?",description:"This will delete the project and all its tasks.",confirmLabel:"Delete",variant:"danger",onConfirm:()=>eN(),onCancel:()=>S(null)}),C?.type==="delete-task"&&(0,s.jsx)("div",{className:"fixed inset-0 z-50 flex items-center justify-center",style:{background:"rgba(0,0,0,0.5)",backdropFilter:"blur(2px)"},children:(0,s.jsxs)("div",{className:"bg-card border border-border rounded-xl shadow-2xl shadow-black/40 w-full max-w-sm mx-4 animate-dialog-in",children:[(0,s.jsxs)("div",{className:"p-5",children:[(0,s.jsx)("h3",{className:"text-sm font-semibold text-foreground",children:"Remove task"}),(0,s.jsx)("p",{className:"text-xs text-muted-foreground mt-1.5 leading-relaxed",children:"보관함에 넣으면 나중에 복원하거나 프롬프트를 참고할 수 있습니다."})]}),(0,s.jsxs)("div",{className:"flex justify-end gap-2 px-5 pb-4",children:[(0,s.jsx)("button",{onClick:()=>S(null),className:"px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground bg-muted hover:bg-card-hover border border-border rounded-md transition-colors",children:"Cancel"}),(0,s.jsx)("button",{onClick:()=>eN("permanent"),className:"px-3 py-1.5 text-xs text-white bg-destructive hover:bg-destructive/80 rounded-md transition-colors",children:"Delete"}),(0,s.jsx)("button",{onClick:()=>eN("archive"),className:"px-3 py-1.5 text-xs text-white bg-primary hover:bg-primary-hover rounded-md transition-colors",children:"Archive"})]})]})}),(0,s.jsx)(eF,{open:_,content:m.ai_context||"",onSave:ew,onClose:()=>L(!1)}),(0,s.jsx)(ez,{open:!!B,results:B||[],onClose:()=>W(null)}),H&&m.project_path&&(0,s.jsx)(eB,{rootPath:m.project_path,onClose:()=>q(!1)}),(0,s.jsx)(eW,{open:V,projectId:e,onClose:()=>X(!1),onApplied:()=>{eu()}}),O&&(0,s.jsx)(eZ,{projectId:e,projectName:m.name,onClose:()=>R(!1)}),Y&&(0,s.jsxs)("div",{className:"fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-card border border-border rounded-lg shadow-xl px-4 py-2.5 flex items-center gap-3 animate-dialog-in",children:[(0,s.jsxs)("span",{className:"text-xs text-muted-foreground truncate max-w-[200px]",children:['"',Y.title,'" 삭제됨']}),(0,s.jsx)("button",{onClick:ev,className:"text-xs px-2 py-1 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity",children:"되돌리기"}),(0,s.jsx)("button",{onClick:()=>{clearTimeout(Y.timer),ee(null)},className:"text-xs text-muted-foreground hover:text-foreground",children:"\xd7"})]})]}):(0,s.jsx)("div",{className:"flex-1 flex items-center justify-center text-muted-foreground",children:"Loading..."})}function eY(){let[e,t]=(0,n.useState)(!1),[r,o]=(0,n.useState)(""),[a,l]=(0,n.useState)([]),[i,d]=(0,n.useState)(0),[c,u]=(0,n.useState)(!1),m=(0,n.useRef)(null),{openProject:p}=x(),h=(0,n.useRef)(null);(0,n.useEffect)(()=>{let e=e=>{(e.metaKey||e.ctrlKey)&&"p"===e.key.toLowerCase()&&(e.preventDefault(),t(e=>!e))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[]),(0,n.useEffect)(()=>{if(!e)return;o(""),l([]),d(0);let t=requestAnimationFrame(()=>m.current?.focus());return()=>cancelAnimationFrame(t)},[e]),(0,n.useEffect)(()=>{if(!e)return;let t=r.trim();if(t.length<1){l([]),u(!1);return}u(!0);let s=setTimeout(()=>{h.current?.abort();let e=new AbortController;h.current=e,fetch(`/api/search?q=${encodeURIComponent(t)}`,{signal:e.signal}).then(e=>e.ok?e.json():[]).then(e=>{l(Array.isArray(e)?e:[]),d(0),u(!1)}).catch(()=>{})},120);return()=>clearTimeout(s)},[r,e]);let f=(0,n.useCallback)(e=>{p(e.projectId,e.projectName,e.subProjectId,e.taskId),t(!1)},[p]);return e?(0,s.jsx)("div",{onClick:()=>t(!1),className:"fixed inset-0 z-[60] flex items-start justify-center pt-[14vh]",style:{background:"rgba(0,0,0,0.5)",backdropFilter:"blur(3px)"},children:(0,s.jsxs)("div",{onClick:e=>e.stopPropagation(),className:"bg-card border border-border rounded-xl shadow-2xl w-full max-w-xl animate-dialog-in",children:[(0,s.jsxs)("div",{className:"px-4 py-3 border-b border-border flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-muted-foreground",children:"\uD83D\uDD0E"}),(0,s.jsx)("input",{ref:m,value:r,onChange:e=>o(e.target.value),onKeyDown:e=>{if("Escape"===e.key)return void t(!1);if("ArrowDown"===e.key){e.preventDefault(),d(e=>Math.min(e+1,a.length-1));return}if("ArrowUp"===e.key){e.preventDefault(),d(e=>Math.max(e-1,0));return}if("Enter"===e.key){e.preventDefault();let t=a[i];t&&f(t)}},placeholder:"태스크 \xb7 프로젝트 \xb7 워크스페이스 검색… (⌘P)",className:"flex-1 bg-transparent text-sm text-foreground focus:outline-none"}),(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/70 px-1.5 py-0.5 border border-border rounded",children:"↑↓ \xb7 ↵ \xb7 Esc"})]}),(0,s.jsxs)("div",{className:"max-h-[55vh] overflow-y-auto",children:[c&&(0,s.jsx)("div",{className:"px-4 py-6 text-xs text-muted-foreground",children:"검색 중…"}),!c&&r.trim()&&0===a.length&&(0,s.jsx)("div",{className:"px-4 py-6 text-xs text-muted-foreground",children:"일치하는 항목 없음"}),!c&&!r.trim()&&(0,s.jsx)("div",{className:"px-4 py-6 text-xs text-muted-foreground",children:"무엇을 찾으시나요? 태스크 제목\xb7본문, 프로젝트\xb7워크스페이스 이름을 검색합니다."}),(0,s.jsx)("ul",{children:a.map((e,t)=>(0,s.jsxs)("li",{onMouseEnter:()=>d(t),onClick:()=>f(e),className:`px-4 py-2.5 cursor-pointer border-l-2 ${t===i?"bg-muted border-primary":"border-transparent"}`,children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[(0,s.jsx)("span",{className:`px-1.5 py-0.5 rounded text-[10px] uppercase tracking-wide ${"task"===e.type?"bg-primary/15 text-primary":"project"===e.type?"bg-accent/15 text-accent":"bg-warning/15 text-warning"}`,children:"sub-project"===e.type?"project":e.type}),(0,s.jsx)("span",{children:e.projectName}),e.subProjectName&&"task"===e.type&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"opacity-50",children:"›"}),(0,s.jsx)("span",{children:e.subProjectName})]}),e.isArchived&&(0,s.jsx)("span",{className:"text-muted-foreground/70 italic",children:"(archived)"})]}),(0,s.jsx)("div",{className:"text-sm text-foreground mt-0.5 truncate",children:e.title}),e.snippet&&(0,s.jsx)("div",{className:"text-xs text-muted-foreground/80 mt-0.5 truncate",children:e.snippet})]},`${e.type}-${e.taskId??e.subProjectId??e.projectId}`))})]})]})}):null}let e0="im-quick-capture-last-dest";function e1(){try{let e=localStorage.getItem(e0);if(!e)return null;let t=JSON.parse(e);if(t?.projectId&&t?.subProjectId)return t}catch{}return null}function e2(){let[e,t]=(0,n.useState)(!1),[r,o]=(0,n.useState)([]),[a,l]=(0,n.useState)(""),[i,d]=(0,n.useState)(""),[c,u]=(0,n.useState)(""),[m,p]=(0,n.useState)(!1),[h,f]=(0,n.useState)(null),g=(0,n.useRef)(null),{openProject:b}=x();(0,n.useEffect)(()=>{let e=e=>{(e.metaKey||e.ctrlKey)&&"n"===e.key.toLowerCase()&&(e.preventDefault(),t(e=>!e))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[]),(0,n.useEffect)(()=>{e&&fetch("/api/projects").then(e=>e.ok?e.json():[]).then(e=>{o(e.map(e=>({...e})));let t=e1(),r=e[0]?.id,s=t?.projectId&&e.some(e=>e.id===t.projectId)?t.projectId:r;s&&l(s)})},[e]),(0,n.useEffect)(()=>{if(!a)return void d("");let e=r.find(e=>e.id===a);if(e?.loaded&&e.subProjects){let t=e1(),r=e.subProjects[0]?.id??"";d(t?.projectId===a&&e.subProjects.some(e=>e.id===t.subProjectId)?t.subProjectId:r);return}fetch(`/api/projects/${a}/sub-projects`).then(e=>e.ok?e.json():[]).then(e=>{o(t=>t.map(t=>t.id===a?{...t,subProjects:e,loaded:!0}:t));let t=e1(),r=e[0]?.id??"";d(t?.projectId===a&&e.some(e=>e.id===t.subProjectId)?t.subProjectId:r)})},[a]),(0,n.useEffect)(()=>{if(!e)return;u(""),f(null),p(!1);let t=requestAnimationFrame(()=>g.current?.focus());return()=>cancelAnimationFrame(t)},[e]);let j=async()=>{let e=c.trim();if(e&&a&&i&&!m){p(!0),f(null);try{let s=await fetch(`/api/projects/${a}/sub-projects/${i}/tasks`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:e})});if(!s.ok)throw Error(`HTTP ${s.status}`);let n=await s.json();try{localStorage.setItem(e0,JSON.stringify({projectId:a,subProjectId:i}))}catch{}let o=r.find(e=>e.id===a);o&&b(o.id,o.name,i,n.id),t(!1)}catch(e){f(e instanceof Error?e.message:"생성 실패"),p(!1)}}};if(!e)return null;let y=r.find(e=>e.id===a),v=y?.subProjects??[];return(0,s.jsx)("div",{onClick:()=>t(!1),className:"fixed inset-0 z-[60] flex items-start justify-center pt-[16vh]",style:{background:"rgba(0,0,0,0.5)",backdropFilter:"blur(3px)"},children:(0,s.jsxs)("div",{onClick:e=>e.stopPropagation(),className:"bg-card border border-border rounded-xl shadow-2xl w-full max-w-md animate-dialog-in p-4 flex flex-col gap-3",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsx)("div",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"빠른 태스크 캡처"}),(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/70 px-1.5 py-0.5 border border-border rounded",children:"⌘N \xb7 Esc"})]}),(0,s.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,s.jsx)("select",{value:a,onChange:e=>l(e.target.value),className:"bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-none",children:r.map(e=>(0,s.jsx)("option",{value:e.id,children:e.name},e.id))}),(0,s.jsx)("select",{value:i,onChange:e=>d(e.target.value),disabled:!v.length,className:"bg-input border border-border rounded-md px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-none disabled:opacity-50",children:0===v.length?(0,s.jsx)("option",{value:"",children:"프로젝트 없음"}):v.map(e=>(0,s.jsx)("option",{value:e.id,children:e.name},e.id))})]}),(0,s.jsx)("input",{ref:g,value:c,onChange:e=>u(e.target.value),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),j()),"Escape"===e.key&&(e.preventDefault(),t(!1))},placeholder:"태스크 제목을 입력하고 Enter…",className:"w-full bg-input border border-border rounded-md px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none"}),h&&(0,s.jsxs)("div",{className:"text-xs text-destructive",children:["⚠ ",h]}),(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsx)("div",{className:"text-[10px] text-muted-foreground/70",children:"저장 후 해당 태스크 워크스페이스로 바로 이동"}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)("button",{onClick:()=>t(!1),className:"text-xs text-muted-foreground px-2 py-1",children:"취소"}),(0,s.jsx)("button",{onClick:j,disabled:!c.trim()||!a||!i||m,className:"text-xs px-3 py-1 bg-primary text-primary-foreground rounded disabled:opacity-40",children:m?"…":"생성"})]})]})]})})}let e5=[{title:"전역",shortcuts:[{keys:"⌘P",desc:"전역 검색"},{keys:"⌘N",desc:"빠른 태스크 생성"},{keys:"⌘M",desc:"전역 메모 (Quick Memo)"},{keys:"⌘J",desc:"전역 AI 어드바이저"},{keys:"?",desc:"이 도움말"}]},{title:"워크스페이스",shortcuts:[{keys:"⌘L",desc:"Project Advisor 열기/닫기"},{keys:"B",desc:"브레인스토밍 패널 토글"},{keys:"N",desc:"새 프로젝트 추가"},{keys:"T",desc:"새 태스크 추가"},{keys:"⌘1",desc:"상태 → Idea"},{keys:"⌘2",desc:"상태 → Doing"},{keys:"⌘3",desc:"상태 → Done"},{keys:"⌘4",desc:"상태 → Problem"}]},{title:"노트 에디터",shortcuts:[{keys:"⌘K",desc:"AI 명령 팔레트"},{keys:"⌘⇧T",desc:"체크박스/불릿 → 태스크 승격"},{keys:"⌘⇧F",desc:"포커스 모드 (노트 풀스크린)"},{keys:"/",desc:"슬래시 명령 (/todo, /table, /code…)"},{keys:"⌘↵",desc:"체크박스 토글 [ ] ↔ [x]"},{keys:"⌘⇧↵",desc:"테이블 행 추가"},{keys:"⌘⇧⌫",desc:"테이블 행 삭제"},{keys:"Tab",desc:"고스트 자동완성 수락"},{keys:"Esc",desc:"고스트 해제"},{keys:"Enter",desc:"리스트 자동 이어쓰기"}]}];function e3(){let[e,t]=(0,n.useState)(!1);return((0,n.useEffect)(()=>{let r=r=>{if("?"===r.key&&!r.metaKey&&!r.ctrlKey&&!r.altKey){let e=r.target;if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e?.isContentEditable||e?.closest?.(".cm-editor"))return;r.preventDefault(),t(e=>!e)}"Escape"===r.key&&e&&t(!1)};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[e]),e)?(0,s.jsx)("div",{onClick:()=>t(!1),className:"fixed inset-0 z-[70] flex items-center justify-center",style:{background:"rgba(0,0,0,0.5)",backdropFilter:"blur(3px)"},children:(0,s.jsxs)("div",{onClick:e=>e.stopPropagation(),className:"bg-card border border-border rounded-xl shadow-2xl w-full max-w-lg animate-dialog-in p-5",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-4",children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-foreground",children:"Keyboard Shortcuts"}),(0,s.jsx)("button",{onClick:()=>t(!1),className:"text-muted-foreground hover:text-foreground text-lg leading-none",children:"\xd7"})]}),(0,s.jsx)("div",{className:"space-y-4",children:e5.map(e=>(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-[10px] uppercase tracking-wider text-muted-foreground/70 mb-1.5",children:e.title}),(0,s.jsx)("div",{className:"grid grid-cols-[auto_1fr] gap-x-4 gap-y-1",children:e.shortcuts.map(e=>(0,s.jsxs)("div",{className:"contents",children:[(0,s.jsx)("kbd",{className:"text-xs font-mono px-1.5 py-0.5 rounded bg-muted border border-border text-foreground text-right whitespace-nowrap",children:e.keys}),(0,s.jsx)("span",{className:"text-xs text-muted-foreground self-center",children:e.desc})]},e.keys))})]},e.title))}),(0,s.jsx)("div",{className:"mt-4 text-[10px] text-muted-foreground/50 text-center",children:"? 를 다시 눌러 닫기"})]})}):null}function e4(){let[e,t]=(0,n.useState)(!1),[r,o]=(0,n.useState)(""),[a,l]=(0,n.useState)(!1),i=(0,n.useRef)(null);(0,n.useEffect)(()=>{let e=e=>{(e.metaKey||e.ctrlKey)&&"m"===e.key.toLowerCase()&&(e.preventDefault(),t(e=>!e))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[]),(0,n.useEffect)(()=>{e&&!a&&fetch("/api/global-memo").then(e=>e.ok?e.json():{content:""}).then(e=>{o(e.content||""),l(!0)}).catch(()=>l(!0))},[e,a]);let d=(0,n.useCallback)(e=>{i.current&&clearTimeout(i.current),i.current=setTimeout(()=>{fetch("/api/global-memo",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:e})}).then(()=>{window.dispatchEvent(new Event("global-memo-updated"))}).catch(()=>{})},600)},[]);return((0,n.useEffect)(()=>{let t=()=>{e&&fetch("/api/global-memo").then(e=>e.ok?e.json():null).then(e=>{e?.content!==void 0&&o(e.content)}).catch(()=>{})};return window.addEventListener("global-memo-updated",t),()=>window.removeEventListener("global-memo-updated",t)},[e]),e)?(0,s.jsx)("div",{onClick:()=>t(!1),className:"fixed inset-0 z-[55] flex items-center justify-center",style:{background:"rgba(0,0,0,0.3)",backdropFilter:"blur(2px)"},children:(0,s.jsxs)("div",{onClick:e=>e.stopPropagation(),className:"bg-card border border-border rounded-xl shadow-2xl w-[520px] max-w-[90vw] h-[65vh] max-h-[550px] flex flex-col animate-dialog-in",children:[(0,s.jsxs)("div",{className:"px-4 py-2.5 border-b border-border flex items-center justify-between flex-shrink-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("span",{className:"text-sm font-semibold text-foreground",children:"Quick Memo"}),(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/60",children:"⌘M"})]}),(0,s.jsx)("button",{onClick:()=>t(!1),className:"text-muted-foreground hover:text-foreground text-lg leading-none",children:"\xd7"})]}),(0,s.jsx)("div",{className:"flex-1 min-h-0 p-1",children:(0,s.jsx)("textarea",{value:r,onChange:e=>{let t=e.target.value;o(t),d(t)},placeholder:"자유롭게 메모하세요… 전역 스크래치패드입니다.",className:"w-full h-full bg-transparent text-sm text-foreground resize-none focus:outline-none p-3 font-mono leading-relaxed",autoFocus:!0})}),(0,s.jsx)("div",{className:"px-4 py-1.5 border-t border-border text-[10px] text-muted-foreground/50 flex-shrink-0",children:"자동 저장 \xb7 Esc로 닫기"})]})}):null}function e6(){let[e,t]=(0,n.useState)(!1);return((0,n.useEffect)(()=>{let e=e=>{(e.metaKey||e.ctrlKey)&&"j"===e.key.toLowerCase()&&(e.preventDefault(),t(e=>!e))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[]),e)?(0,s.jsx)(eX,{basePath:"/api/global-advisor",title:"Global Advisor",shortcutHint:"⌘J",placeholder:"전체 워크스페이스에 대해 물어보세요…",emptyIcon:"\uD83C\uDF10",emptyHints:["모든 프로젝트를 조망하고 답합니다",'"전체 진행 상황 요약해줘"\n"어떤 프로젝트가 제일 급해?"\n"이번 주 뭐 해야 돼?"'],activityType:"global-advisor",activityLabel:"Global Advisor",onClose:()=>t(!1)}):null}function e8(){let{state:e}=x();return(0,s.jsxs)("div",{className:"h-screen flex flex-col",children:[(0,s.jsx)(_,{}),(0,s.jsx)(eY,{}),(0,s.jsx)(e2,{}),(0,s.jsx)(e3,{}),(0,s.jsx)(e4,{}),(0,s.jsx)(e6,{}),(0,s.jsx)("div",{className:"flex-1 min-h-0 relative",children:e.tabs.map(t=>(0,s.jsx)("div",{className:"absolute inset-0 flex flex-col",style:{display:t.id===e.activeTabId?"flex":"none"},children:"dashboard"===t.type?(0,s.jsx)(K,{}):(0,s.jsx)(eQ,{id:t.projectId,initialSubId:t.initialSubId,initialTaskId:t.initialTaskId})},t.id))})]})}function e7(){return(0,s.jsx)(j,{children:(0,s.jsx)(m,{children:(0,s.jsx)(e8,{})})})}}},e=>{e.O(0,[79,374,441,794,358],()=>e(e.s=3915)),_N_E=e.O()}]);
|
package/bin/postinstall.js
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const isWindows = process.platform === 'win32';
|
|
4
7
|
|
|
5
8
|
try {
|
|
6
|
-
|
|
9
|
+
// Check if `im` is on PATH. Windows uses `where`, POSIX uses `command -v`.
|
|
10
|
+
execSync(isWindows ? 'where im' : 'command -v im', { stdio: 'ignore', shell: true });
|
|
7
11
|
} catch {
|
|
8
|
-
|
|
12
|
+
let npmPrefix = '';
|
|
13
|
+
try {
|
|
14
|
+
npmPrefix = execSync('npm prefix -g', { encoding: 'utf-8' }).trim();
|
|
15
|
+
} catch { /* fall through */ }
|
|
16
|
+
|
|
17
|
+
console.log('\n \u2713 idea-manager installed!\n');
|
|
18
|
+
console.log(' \u26A0 "im" command not found in PATH.\n');
|
|
9
19
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
if (isWindows) {
|
|
21
|
+
// On Windows, npm global prefix is the directory holding the .cmd wrappers (no "bin" subdir).
|
|
22
|
+
const dir = npmPrefix || '%APPDATA%\\npm';
|
|
23
|
+
console.log(` Add this directory to your PATH:\n`);
|
|
24
|
+
console.log(` ${dir}\n`);
|
|
25
|
+
console.log(' PowerShell (User PATH):');
|
|
26
|
+
console.log(` [Environment]::SetEnvironmentVariable('Path', "$env:Path;${dir}", 'User')\n`);
|
|
27
|
+
console.log(' Or via System Properties > Environment Variables.');
|
|
28
|
+
console.log(' Open a new terminal and run: im start\n');
|
|
29
|
+
} else {
|
|
30
|
+
const binDir = npmPrefix ? path.join(npmPrefix, 'bin') : '$(npm prefix -g)/bin';
|
|
31
|
+
console.log(' Add to your shell profile (~/.zshrc or ~/.bashrc):\n');
|
|
32
|
+
console.log(` export PATH="${binDir}:$PATH"\n`);
|
|
33
|
+
console.log(' Then open a new terminal and run: im start\n');
|
|
34
|
+
}
|
|
15
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idea-manager",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "AI-powered idea & task manager for developers. Brainstorm → structured tasks → AI agent execution. CodeMirror editor with slash commands, MCP Server, Claude/Gemini/Codex support. Local-first SQLite, cross-PC Git sync.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"idea-manager",
|
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Force UTF-8 output on Windows so Korean strings don't garble in cp949 terminals.
|
|
4
|
+
if (process.platform === 'win32') {
|
|
5
|
+
try {
|
|
6
|
+
process.stdout.setDefaultEncoding?.('utf8');
|
|
7
|
+
process.stderr.setDefaultEncoding?.('utf8');
|
|
8
|
+
} catch { /* non-critical */ }
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
import { Command } from 'commander';
|
|
4
12
|
import { ensureDb } from './lib/db';
|
|
5
13
|
import { startMcpServer } from './lib/mcp/server';
|
|
@@ -12,6 +20,7 @@ import { startWatcher } from './lib/watcher';
|
|
|
12
20
|
import { syncInit, syncPush, syncPull, syncStatus } from './lib/sync/index';
|
|
13
21
|
import { maybeAutoUpdate, respawnSelf } from './lib/auto-update';
|
|
14
22
|
import { spawn } from 'child_process';
|
|
23
|
+
import { readFileSync } from 'fs';
|
|
15
24
|
import path from 'path';
|
|
16
25
|
import { fileURLToPath } from 'url';
|
|
17
26
|
|
|
@@ -41,6 +50,9 @@ async function openAsApp(url: string) {
|
|
|
41
50
|
]
|
|
42
51
|
: platform === 'win32'
|
|
43
52
|
? [
|
|
53
|
+
{ bin: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', args: [`--app=${url}`] },
|
|
54
|
+
{ bin: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', args: [`--app=${url}`] },
|
|
55
|
+
{ bin: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe', args: [`--app=${url}`] },
|
|
44
56
|
{ bin: 'chrome', args: [`--app=${url}`] },
|
|
45
57
|
{ bin: 'msedge', args: [`--app=${url}`] },
|
|
46
58
|
]
|
|
@@ -51,7 +63,9 @@ async function openAsApp(url: string) {
|
|
|
51
63
|
];
|
|
52
64
|
|
|
53
65
|
for (const browser of browsers) {
|
|
54
|
-
|
|
66
|
+
// Skip absolute paths that don't exist (macOS .app, Windows Program Files)
|
|
67
|
+
const isAbsolute = path.isAbsolute(browser.bin);
|
|
68
|
+
if (isAbsolute && !fs.existsSync(browser.bin)) continue;
|
|
55
69
|
|
|
56
70
|
try {
|
|
57
71
|
const child = spawnChild(browser.bin, browser.args, {
|
|
@@ -73,10 +87,17 @@ async function openAsApp(url: string) {
|
|
|
73
87
|
|
|
74
88
|
const program = new Command();
|
|
75
89
|
|
|
90
|
+
function readPkgVersion(): string {
|
|
91
|
+
try {
|
|
92
|
+
const pkg = JSON.parse(readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')) as { version?: string };
|
|
93
|
+
return pkg.version ?? '0.0.0';
|
|
94
|
+
} catch { return '0.0.0'; }
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
program
|
|
77
98
|
.name('im')
|
|
78
|
-
.description('Idea Manager
|
|
79
|
-
.version(
|
|
99
|
+
.description('Idea Manager - Brainstorming to structured tasks with prompts')
|
|
100
|
+
.version(readPkgVersion());
|
|
80
101
|
|
|
81
102
|
program
|
|
82
103
|
.command('mcp')
|
|
@@ -135,23 +156,35 @@ program
|
|
|
135
156
|
} catch {
|
|
136
157
|
nextCli = path.join(PKG_ROOT, 'node_modules', 'next', 'dist', 'bin', 'next');
|
|
137
158
|
}
|
|
159
|
+
if (!fs.existsSync(nextCli)) {
|
|
160
|
+
console.error('\n ⚠ Next.js 바이너리를 찾을 수 없습니다. 의존성이 손상된 듯합니다.');
|
|
161
|
+
console.error(` 재설치: npm install -g idea-manager@latest\n`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
138
164
|
|
|
139
165
|
// Build if not already built (check BUILD_ID, not just .next dir existence)
|
|
140
166
|
const buildMarker = path.join(PKG_ROOT, '.next', 'BUILD_ID');
|
|
141
167
|
if (!fs.existsSync(buildMarker)) {
|
|
142
168
|
console.log('\n IM - First run: building... (this may take a minute)\n');
|
|
143
|
-
const buildResult = spawn(process.execPath, [nextCli, 'build'], {
|
|
169
|
+
const buildResult = spawn(process.execPath, [nextCli, 'build', '--webpack'], {
|
|
144
170
|
cwd: PKG_ROOT,
|
|
145
171
|
stdio: 'inherit',
|
|
146
172
|
env: { ...process.env, NODE_ENV: 'production' },
|
|
147
173
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
174
|
+
try {
|
|
175
|
+
await new Promise<void>((resolve, reject) => {
|
|
176
|
+
buildResult.on('exit', (code) => {
|
|
177
|
+
if (code !== 0) reject(new Error(`Build failed with code ${code}`));
|
|
178
|
+
else resolve();
|
|
179
|
+
});
|
|
180
|
+
buildResult.on('error', reject);
|
|
152
181
|
});
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(`\n ⚠ 빌드 실패: ${err instanceof Error ? err.message : String(err)}`);
|
|
184
|
+
console.error(` 글로벌 설치 시 devDependencies가 빠졌을 수 있습니다.`);
|
|
185
|
+
console.error(` 시도: cd "${PKG_ROOT}" && npm install\n`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
155
188
|
}
|
|
156
189
|
|
|
157
190
|
console.log(`\n IM - Idea Manager`);
|
package/src/lib/ai/client.ts
CHANGED
|
@@ -37,6 +37,7 @@ export function runAgent(
|
|
|
37
37
|
cwd: options?.cwd || process.cwd(),
|
|
38
38
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
39
|
shell: process.platform === 'win32',
|
|
40
|
+
windowsHide: true,
|
|
40
41
|
env,
|
|
41
42
|
});
|
|
42
43
|
|
|
@@ -50,9 +51,26 @@ export function runAgent(
|
|
|
50
51
|
}, options.timeoutMs);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
//
|
|
54
|
-
proc.
|
|
55
|
-
|
|
54
|
+
// Spawn failure (e.g., binary not found) → reject instead of hanging.
|
|
55
|
+
proc.on('error', (err) => {
|
|
56
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
57
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
58
|
+
if (code === 'ENOENT') {
|
|
59
|
+
reject(new Error(`${config.name} CLI not found on PATH. Install it first.`));
|
|
60
|
+
} else {
|
|
61
|
+
reject(new Error(`${config.name} CLI error: ${err.message}`));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Write prompt to stdin and close it. Wrap in try/catch for broken pipe cases.
|
|
66
|
+
try {
|
|
67
|
+
proc.stdin?.write(prompt, 'utf8');
|
|
68
|
+
proc.stdin?.end();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
71
|
+
reject(new Error(`Failed to pipe prompt to ${config.name}: ${(err as Error).message}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
56
74
|
|
|
57
75
|
let buffer = '';
|
|
58
76
|
let resultText = '';
|
|
@@ -103,10 +121,6 @@ export function runAgent(
|
|
|
103
121
|
stderrText += chunk.toString();
|
|
104
122
|
});
|
|
105
123
|
|
|
106
|
-
proc.on('error', (err) => {
|
|
107
|
-
reject(new Error(`${config.name} CLI error: ${err.message}`));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
124
|
proc.on('exit', (code, signal) => {
|
|
111
125
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
112
126
|
if (!useStreamJson && config.cleanOutput) {
|
package/src/lib/auto-update.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
5
|
function readInstalledVersion(pkgRoot: string): string {
|
|
@@ -120,9 +120,28 @@ export async function maybeAutoUpdate(pkgRoot: string): Promise<AutoUpdateResult
|
|
|
120
120
|
* replaces the old copy loaded in the current Node process. Sets
|
|
121
121
|
* IM_NO_AUTO_UPDATE=1 on the child to prevent an update-respawn loop.
|
|
122
122
|
*/
|
|
123
|
+
function resolveImBin(): string | null {
|
|
124
|
+
// After `npm install -g`, the im wrapper lives in npm's global bin.
|
|
125
|
+
try {
|
|
126
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf-8' }).trim();
|
|
127
|
+
// On Windows the wrapper is `<prefix>\im.cmd`; on POSIX it's `<prefix>/bin/im`.
|
|
128
|
+
const candidates = process.platform === 'win32'
|
|
129
|
+
? [join(prefix, 'im.cmd'), join(prefix, 'im.ps1'), join(prefix, 'im')]
|
|
130
|
+
: [join(prefix, 'bin', 'im')];
|
|
131
|
+
for (const c of candidates) {
|
|
132
|
+
if (existsSync(c)) return c;
|
|
133
|
+
}
|
|
134
|
+
} catch { /* fall through */ }
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
export function respawnSelf(): void {
|
|
124
139
|
const args = process.argv.slice(2);
|
|
125
|
-
const
|
|
140
|
+
const binPath = resolveImBin();
|
|
141
|
+
const cmd = binPath ?? 'im';
|
|
142
|
+
// On Windows, .cmd wrappers require shell:true (cmd.exe interprets them).
|
|
143
|
+
// On POSIX, prefer shell:false with an absolute path for reliability.
|
|
144
|
+
const child = spawn(cmd, args, {
|
|
126
145
|
stdio: 'inherit',
|
|
127
146
|
shell: process.platform === 'win32',
|
|
128
147
|
env: { ...process.env, IM_NO_AUTO_UPDATE: '1' },
|
package/src/lib/db/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import { getDbPath } from '../utils/paths';
|
|
3
4
|
import { initSchema } from './schema';
|
|
4
5
|
import { initScheduler } from '../scheduler';
|
|
@@ -20,7 +21,17 @@ class DatabaseWrapper {
|
|
|
20
21
|
private save() {
|
|
21
22
|
if (!this.dirty) return;
|
|
22
23
|
const data = this.db.export();
|
|
23
|
-
|
|
24
|
+
// Atomic write: write to temp file, then rename. Prevents corruption
|
|
25
|
+
// if the process dies mid-write.
|
|
26
|
+
const tmpPath = `${this.dbPath}.tmp-${process.pid}`;
|
|
27
|
+
try {
|
|
28
|
+
fs.writeFileSync(tmpPath, Buffer.from(data));
|
|
29
|
+
fs.renameSync(tmpPath, this.dbPath);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
// Best-effort cleanup of orphan tmp
|
|
32
|
+
try { fs.unlinkSync(tmpPath); } catch { /* ignore */ }
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
24
35
|
this.dirty = false;
|
|
25
36
|
}
|
|
26
37
|
|
|
@@ -144,12 +155,25 @@ async function initAsync(): Promise<DatabaseWrapper> {
|
|
|
144
155
|
|
|
145
156
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
146
157
|
const initSqlJs = require('sql.js/dist/sql-wasm.js');
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
158
|
+
// Explicitly locate the .wasm next to sql-wasm.js — otherwise sql.js falls
|
|
159
|
+
// back to `__dirname` which can be wrong when invoked from MCP stdio mode
|
|
160
|
+
// (cwd may differ from PKG_ROOT).
|
|
161
|
+
let wasmDir: string;
|
|
162
|
+
try {
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
164
|
+
wasmDir = path.dirname(require.resolve('sql.js/dist/sql-wasm.js'));
|
|
165
|
+
} catch {
|
|
166
|
+
wasmDir = '';
|
|
167
|
+
}
|
|
168
|
+
const SQL = await initSqlJs(wasmDir ? {
|
|
169
|
+
locateFile: (file: string) => path.join(wasmDir, file),
|
|
170
|
+
} : undefined);
|
|
151
171
|
|
|
152
172
|
const dbPath = getDbPath();
|
|
173
|
+
// Ensure parent directory exists (first-run safety on fresh installs).
|
|
174
|
+
const dbDir = path.dirname(dbPath);
|
|
175
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
176
|
+
|
|
153
177
|
let db;
|
|
154
178
|
if (fs.existsSync(dbPath)) {
|
|
155
179
|
const fileBuffer = fs.readFileSync(dbPath);
|
package/src/lib/scheduler.ts
CHANGED
|
@@ -10,7 +10,12 @@ function formatDate(d: Date): string {
|
|
|
10
10
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function
|
|
13
|
+
function sendNotification(title: string, message: string) {
|
|
14
|
+
// macOS native notification via osascript. Other platforms get a console log.
|
|
15
|
+
if (process.platform !== 'darwin') {
|
|
16
|
+
console.log(`[${title}] ${message}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
14
19
|
const escaped = message.replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
15
20
|
const script = `display notification "${escaped}" with title "${title}"`;
|
|
16
21
|
exec(`osascript -e '${script}'`, (err) => {
|
|
@@ -60,7 +65,7 @@ async function checkMorningNotification() {
|
|
|
60
65
|
if (activeTasks.count > 0) lines.push(`진행 중: ${activeTasks.count}개`);
|
|
61
66
|
if (problemTasks.count > 0) lines.push(`문제: ${problemTasks.count}개`);
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
sendNotification('IM - 오늘의 할 일', lines.join('\n'));
|
|
64
69
|
lastNotifiedDate = today;
|
|
65
70
|
} catch (err) {
|
|
66
71
|
console.error('[Scheduler] error:', err);
|
|
@@ -71,8 +76,9 @@ export function initScheduler() {
|
|
|
71
76
|
if (initialized) return;
|
|
72
77
|
initialized = true;
|
|
73
78
|
|
|
74
|
-
// Check every minute
|
|
79
|
+
// Check every minute. unref() so it doesn't pin the event loop for MCP stdio mode.
|
|
75
80
|
timer = setInterval(checkMorningNotification, 60 * 1000);
|
|
81
|
+
timer.unref?.();
|
|
76
82
|
|
|
77
83
|
// Also check immediately on startup
|
|
78
84
|
checkMorningNotification();
|
package/src/lib/sync/git.ts
CHANGED
|
@@ -2,24 +2,26 @@ import { execFile } from 'child_process';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!arg.includes(' ') && !arg.includes('"')) return arg;
|
|
8
|
-
if (process.platform === 'win32') {
|
|
9
|
-
// cmd.exe uses "" to escape quotes
|
|
10
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
11
|
-
}
|
|
12
|
-
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
13
|
-
}
|
|
5
|
+
// Longer timeout for network-heavy operations (clone/push/pull over slow links).
|
|
6
|
+
const NETWORK_CMDS = new Set(['clone', 'push', 'pull', 'fetch']);
|
|
14
7
|
|
|
15
8
|
function exec(cmd: string, args: string[], cwd?: string): Promise<string> {
|
|
16
9
|
return new Promise((resolve, reject) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
10
|
+
const isNetwork = args.some(a => NETWORK_CMDS.has(a));
|
|
11
|
+
const timeout = isNetwork ? 300000 : 30000; // 5min / 30s
|
|
12
|
+
// shell: false — Node handles arg escaping natively on all platforms.
|
|
13
|
+
// On Windows, execFile still resolves `.cmd`/`.bat` via PATHEXT for known binaries.
|
|
14
|
+
execFile(cmd, args, {
|
|
15
|
+
cwd,
|
|
16
|
+
timeout,
|
|
17
|
+
shell: process.platform === 'win32',
|
|
18
|
+
windowsHide: true,
|
|
19
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
20
|
+
}, (err, stdout, stderr) => {
|
|
19
21
|
if (err) {
|
|
20
|
-
reject(new Error(stderr?.trim() || err.message));
|
|
22
|
+
reject(new Error(stderr?.toString().trim() || err.message));
|
|
21
23
|
} else {
|
|
22
|
-
resolve(stdout.trim());
|
|
24
|
+
resolve(stdout.toString().trim());
|
|
23
25
|
}
|
|
24
26
|
});
|
|
25
27
|
});
|
|
@@ -43,7 +45,8 @@ export function isGitRepo(dir: string): boolean {
|
|
|
43
45
|
|
|
44
46
|
export async function ghCreateRepo(name: string): Promise<string> {
|
|
45
47
|
// Create private repo and get URL
|
|
46
|
-
|
|
48
|
+
// --confirm was removed in gh 2.x; repo is created non-interactively by default with name+flag
|
|
49
|
+
const result = await exec('gh', ['repo', 'create', name, '--private']);
|
|
47
50
|
// Extract repo URL from output
|
|
48
51
|
const urlMatch = result.match(/https:\/\/github\.com\/\S+/);
|
|
49
52
|
if (urlMatch) return urlMatch[0];
|
package/src/lib/watcher.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { ensureDb } from './db';
|
|
2
|
+
import { ensureDb, getDb } from './db';
|
|
3
3
|
import { runAgent } from './ai/client';
|
|
4
4
|
import { listProjects, getProject } from './db/queries/projects';
|
|
5
5
|
import { getSubProject } from './db/queries/sub-projects';
|
|
@@ -101,7 +101,7 @@ async function executeTask(task: ITask, project: IProject, options: WatcherOptio
|
|
|
101
101
|
const content = `[진행 중]\n${accumulated}`;
|
|
102
102
|
if (progressMsgId) {
|
|
103
103
|
// Update existing progress message
|
|
104
|
-
|
|
104
|
+
// getDb imported at top
|
|
105
105
|
const db = getDb();
|
|
106
106
|
db.prepare('UPDATE task_conversations SET content = ? WHERE id = ?').run(content, progressMsgId);
|
|
107
107
|
} else {
|
|
@@ -135,7 +135,7 @@ async function executeTask(task: ITask, project: IProject, options: WatcherOptio
|
|
|
135
135
|
|
|
136
136
|
// Replace progress message with final result
|
|
137
137
|
if (progressMsgId) {
|
|
138
|
-
|
|
138
|
+
// getDb imported at top
|
|
139
139
|
const db = getDb();
|
|
140
140
|
db.prepare('UPDATE task_conversations SET content = ? WHERE id = ?').run(result || '(no output)', progressMsgId);
|
|
141
141
|
} else {
|
|
@@ -152,7 +152,7 @@ async function executeTask(task: ITask, project: IProject, options: WatcherOptio
|
|
|
152
152
|
|
|
153
153
|
// Replace progress message with error
|
|
154
154
|
if (progressMsgId) {
|
|
155
|
-
|
|
155
|
+
// getDb imported at top
|
|
156
156
|
const db = getDb();
|
|
157
157
|
db.prepare('UPDATE task_conversations SET content = ? WHERE id = ?').run(`[error] ${errorMsg}`, progressMsgId);
|
|
158
158
|
} else {
|
|
File without changes
|
|
File without changes
|