joplin-plugin-aide 1.0.2 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joplin-plugin-aide",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "AI assistant chat panel for Joplin - read, search and edit notes via Claude Code or GitHub Copilot CLI, with per-write confirmation",
5
5
  "author": "lim0513",
6
6
  "homepage": "https://github.com/lim0513/joplin-aide",
package/publish/index.js CHANGED
@@ -1 +1 @@
1
- (()=>{"use strict";var e={317(e){e.exports=require("child_process")},896(e){e.exports=require("fs")},611(e){e.exports=require("http")},928(e){e.exports=require("path")}};const t={};function o(i){const n=t[i];if(void 0!==n)return n.exports;const s=t[i]={exports:{}};return e[i](s,s.exports,o),s.exports}const i={en_US:{inputPlaceholder:"Ask AI about your notes...",titleHistory:"History",titleNew:"New conversation",titleSend:"Send",titleStop:"Stop",titleDelete:"Delete",working:"⏳ Working...",noHistory:"No history yet",approve:"Approve",decline:"Decline",errAlreadyRunning:"A request is already running.",errStartFailed:"Failed to start {bin}: {err}",errProcess:"{bin} process error: {err}. Is the CLI installed and on PATH? (Settings > Joplin Aide)",errExited:"{bin} exited with code {code}: {err}",cCreateNote:'Create note "{title}"',cUpdateNote:"Update note {id}",cRetitle:' (retitle to "{title}")',cBodyChars:" [body: {n} chars]",cCreateNotebook:'Create notebook "{title}"',cDeleteNote:"DELETE note {id}",cToolPermission:"Tool permission: {name}",cAppend:"Append to note {id} [{n} chars]",cTagNote:'Tag note {id} with "{tag}"',cUntagNote:'Remove tag "{tag}" from note {id}',cSetTodo:"Mark to-do {id} as {state}",cUpdateNotebook:"Update notebook {id}",dCreated:"Created note: {title}",dUpdated:"Updated note {id}",dDeleted:"Deleted note {id}",autoApproved:"⚠ Auto-approved: {s}",alwaysAllow:"Always (this session)",titleAttach:"Attach files",titleBackend:"AI backend - click to switch (new session)",backendSwitched:"Switched to {name} - next message starts a new session",errAttachTooBig:"Attachment too large (max 8 MB): {name}",sessionApproved:"✓ Allowed by session rule: {s}"},zh_CN:{inputPlaceholder:"向 AI 询问你的笔记...",titleHistory:"历史会话",titleNew:"新会话",titleSend:"发送",titleStop:"停止",titleDelete:"删除",working:"⏳ 正在处理...",noHistory:"暂无历史会话",approve:"允许",decline:"拒绝",errAlreadyRunning:"已有请求正在运行。",errStartFailed:"启动 {bin} 失败:{err}",errProcess:"{bin} 进程错误:{err}。请确认已安装对应 CLI 且在 PATH 中(设置 > Joplin Aide)。",errExited:"{bin} 退出,代码 {code}:{err}",cCreateNote:"新建笔记「{title}」",cUpdateNote:"修改笔记 {id}",cRetitle:"(改名为「{title}」)",cBodyChars:"(正文 {n} 字符)",cCreateNotebook:"新建笔记本「{title}」",cDeleteNote:"删除笔记 {id}",cToolPermission:"工具权限请求:{name}",cAppend:"追加内容到笔记 {id}({n} 字符)",cTagNote:"给笔记 {id} 加标签「{tag}」",cUntagNote:"移除笔记 {id} 的标签「{tag}」",cSetTodo:"将待办 {id} 标记为{state}",cUpdateNotebook:"修改笔记本 {id}",dCreated:"已创建笔记:{title}",dUpdated:"已修改笔记 {id}",dDeleted:"已删除笔记 {id}",autoApproved:"⚠ 已自动批准:{s}",alwaysAllow:"本会话内一直允许",titleAttach:"添加附件",titleBackend:"AI 后端——点击切换(切换后开新会话)",backendSwitched:"已切换到 {name},下一条消息开始新会话",errAttachTooBig:"附件过大(上限 8 MB):{name}",sessionApproved:"✓ 按会话授权放行:{s}"},ja_JP:{inputPlaceholder:"ノートについて AI に質問...",titleHistory:"履歴",titleNew:"新しい会話",titleSend:"送信",titleStop:"停止",titleDelete:"削除",working:"⏳ 処理中...",noHistory:"履歴はまだありません",approve:"許可",decline:"拒否",errAlreadyRunning:"リクエストがすでに実行中です。",errStartFailed:"{bin} の起動に失敗しました:{err}",errProcess:"{bin} プロセスエラー:{err}。CLI がインストールされ PATH にあるか確認してください(設定 > Joplin Aide)。",errExited:"{bin} がコード {code} で終了:{err}",cCreateNote:"ノート「{title}」を作成",cUpdateNote:"ノート {id} を更新",cRetitle:"(「{title}」に改名)",cBodyChars:"(本文 {n} 文字)",cCreateNotebook:"ノートブック「{title}」を作成",cDeleteNote:"ノート {id} を削除",cToolPermission:"ツール権限リクエスト:{name}",cAppend:"ノート {id} に追記({n} 文字)",cTagNote:"ノート {id} にタグ「{tag}」を追加",cUntagNote:"ノート {id} からタグ「{tag}」を削除",cSetTodo:"タスク {id} を{state}にする",cUpdateNotebook:"ノートブック {id} を変更",dCreated:"ノートを作成:{title}",dUpdated:"ノート {id} を更新しました",dDeleted:"ノート {id} を削除しました",autoApproved:"⚠ 自動許可:{s}",alwaysAllow:"このセッションでは常に許可",titleAttach:"ファイルを添付",titleBackend:"AI バックエンド——クリックで切替(新しい会話を開始)",backendSwitched:"{name} に切り替えました。次のメッセージから新しいセッションになります",errAttachTooBig:"添付ファイルが大きすぎます(上限 8 MB):{name}",sessionApproved:"✓ セッション許可で実行:{s}"}};function n(e,t){return String(e).replace(/\{(\w+)\}/g,(e,o)=>void 0!==t[o]?String(t[o]):e)}const s=o(611),a=o(896),r=o(928),l=o(317);function d(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}joplin.plugins.register({onStart:async function(){const e=function(e){if(i[e])return i[e];const t=String(e||"").split("_")[0];return"zh"===t?i.zh_CN:"ja"===t?i.ja_JP:i.en_US}(await joplin.settings.globalValue("locale")||"en_US");await joplin.settings.registerSection("joplinAide",{label:"Joplin Aide",iconName:"fas fa-robot"}),await joplin.settings.registerSettings({backend:{section:"joplinAide",type:2,value:"claude",public:!0,isEnum:!0,options:{claude:"Claude Code",copilot:"GitHub Copilot"},label:"AI backend",description:"Which local CLI powers the chat. Each must be installed and logged in on its own (claude / copilot). Switching backends starts a new session."},requireWriteConfirm:{section:"joplinAide",type:3,value:!0,public:!0,label:"Confirm before the AI modifies notes",description:"Create/update/delete operations wait for your approval in the chat panel. Applies to both backends."},claudePath:{section:"joplinAide",type:2,value:"",public:!0,subType:"file_path",label:"— Claude · CLI path (claude)",description:'Full path to the claude executable. Leave empty to use "claude" from the system PATH.'},claudeModel:{section:"joplinAide",type:2,value:"",public:!0,label:"Claude · model (optional)",description:"Passed as --model. Leave empty to use the CLI default."},extraAllowedTools:{section:"joplinAide",type:2,value:"WebSearch,WebFetch,Read",public:!0,label:"Claude · additional allowed tools",description:"Comma-separated Claude Code tools to auto-allow besides the Joplin note tools. Tools NOT listed here trigger an Approve/Decline card in the chat panel. Default: WebSearch,WebFetch,Read (Read is needed to view chat and note attachments without prompting)."},extraCliArgs:{section:"joplinAide",type:2,value:"",public:!0,label:"Claude · extra CLI arguments",description:"Advanced: appended verbatim to the claude command line."},copilotPath:{section:"joplinAide",type:2,value:"",public:!0,subType:"file_path",label:"— Copilot · CLI path (copilot)",description:'Full path to the copilot executable. Leave empty to use "copilot" from the system PATH.'},copilotModel:{section:"joplinAide",type:2,value:"",public:!0,label:"Copilot · model (optional)",description:"Passed as --model to the Copilot CLI. Leave empty for automatic model selection."},copilotAllowTools:{section:"joplinAide",type:2,value:"url,write",public:!0,label:"Copilot · additional allowed tools",description:"Comma-separated permission patterns passed as --allow-tool (kinds: url(domain?), write, shell(cmd:*)). Copilot has no approval prompt: anything not listed is denied automatically. Default: url,write (web fetch + local file writes; note edits go through the Joplin tools and confirmation cards regardless). File reads need no entry."},copilotExtraArgs:{section:"joplinAide",type:2,value:"",public:!0,label:"Copilot · extra CLI arguments",description:"Advanced: appended verbatim to the copilot command line."},autoApproveAll:{section:"joplinAide",type:3,value:!1,public:!0,advanced:!0,label:"🔴 ⚠ AUTO MODE — approve ALL permission requests ⚠",description:"DANGER: when enabled, EVERY request is approved automatically without asking - note edits and deletions, AND any tool the AI asks for (potentially including shell commands). The AI gets free rein over your notes. Equivalent to running the CLI with permissions disabled. Leave OFF unless you fully accept the risk."}});const t=await joplin.views.panels.create("aideChatPanel");async function o(){try{const e=await joplin.workspace.selectedNote();p({name:"noteContext",title:e?e.title:""})}catch(e){}}function p(e){joplin.views.panels.postMessage(t,e)}await joplin.views.panels.addScript(t,"webview/panel.css"),await joplin.views.panels.addScript(t,"webview/markdown-it.min.js"),await joplin.views.panels.addScript(t,"webview/panel.js"),await joplin.views.panels.setHtml(t,['<div id="aide-root" data-i18n="'+d(JSON.stringify(e))+'">',' <div class="cc-header"><span class="cc-title">Aide</span>',' <span id="cc-note-context" class="cc-note-context"></span>',' <button id="cc-backend" title="'+d(e.titleBackend)+'"></button>',' <button id="cc-history" title="'+d(e.titleHistory)+'">&#x1F550;</button>',' <button id="cc-new" title="'+d(e.titleNew)+'">&#x2795;</button>'," </div>",' <div id="cc-messages"></div>',' <div id="cc-confirm"></div>',' <div id="cc-attachments"></div>',' <div class="cc-input-row">',' <textarea id="cc-input" rows="3" placeholder="'+d(e.inputPlaceholder)+'"></textarea>',' <div class="cc-input-buttons">',' <button id="cc-attach" title="'+d(e.titleAttach)+'">&#x1F4CE;</button>',' <input id="cc-file" type="file" multiple style="display:none;" />',' <button id="cc-send" title="'+d(e.titleSend)+'">&#x27A4;</button>',' <button id="cc-stop" title="'+d(e.titleStop)+'" style="display:none;">&#x25A0;</button>'," </div>"," </div>","</div>"].join("")),await joplin.views.panels.show(t,!1),await joplin.commands.register({name:"toggleAidePanel",label:"Toggle Aide panel",iconName:"fas fa-robot",execute:async()=>{const e=await joplin.views.panels.visible(t);await joplin.views.panels.show(t,!e)}}),await joplin.views.toolbarButtons.create("aidePanelButton","toggleAidePanel","noteToolbar"),await joplin.workspace.onNoteSelectionChange(o);const c=[{name:"list_notebooks",description:"List all notebooks (folders) with id, title and parent_id.",inputSchema:{type:"object",properties:{},required:[]}},{name:"list_notes",description:"List notes in a notebook (id + title, most recently updated first, max 200).",inputSchema:{type:"object",properties:{notebook_id:{type:"string",description:"Notebook id"}},required:["notebook_id"]}},{name:"search_notes",description:'Search notes with Joplin query syntax: plain words, "exact phrase", tag:xxx, notebook:xxx, type:todo, iscompleted:0, created:20260101, updated:day-7, sourceurl:*. Returns id, title, parent_id, todo fields (max 50).',inputSchema:{type:"object",properties:{query:{type:"string",description:"Search query"}},required:["query"]}},{name:"read_note",description:"Read a note: title, markdown body, notebook id.",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"get_selected_note",description:'Get the note currently open in the Joplin editor (id, title, body). Use this when the user says "this note".',inputSchema:{type:"object",properties:{},required:[]}},{name:"create_note",description:"Create a new note in a notebook.",write:!0,confirmSummary:t=>n(e.cCreateNote,{title:t.title||"(untitled)"}),inputSchema:{type:"object",properties:{title:{type:"string"},body:{type:"string",description:"Markdown body"},notebook_id:{type:"string",description:"Target notebook id (optional; defaults to the currently selected notebook)"}},required:["title","body"]}},{name:"update_note",description:"Update an existing note. Only the provided fields are changed. To edit content, read the note first, then send the FULL new body.",write:!0,confirmSummary:t=>n(e.cUpdateNote,{id:t.note_id})+(t.title?n(e.cRetitle,{title:t.title}):"")+(void 0!==t.body?n(e.cBodyChars,{n:String(t.body).length}):""),inputSchema:{type:"object",properties:{note_id:{type:"string"},title:{type:"string"},body:{type:"string",description:"Full replacement markdown body"},notebook_id:{type:"string",description:"Move to this notebook"}},required:["note_id"]}},{name:"create_notebook",description:"Create a new notebook, optionally under a parent notebook.",write:!0,confirmSummary:t=>n(e.cCreateNotebook,{title:t.title||""}),inputSchema:{type:"object",properties:{title:{type:"string"},parent_id:{type:"string",description:"Parent notebook id (optional)"}},required:["title"]}},{name:"list_tags",description:"List all tags (id + title).",inputSchema:{type:"object",properties:{},required:[]}},{name:"get_note_tags",description:"List the tags attached to a note.",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"list_notes_by_tag",description:"List notes that carry a tag (by tag id, see list_tags).",inputSchema:{type:"object",properties:{tag_id:{type:"string"}},required:["tag_id"]}},{name:"list_note_attachments",description:"List the file attachments (resources) of a note, with their LOCAL file paths. Use the Read tool with local_path to view an attachment (images, PDFs, text...).",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"open_note",description:"Open a note in the Joplin editor (navigate the user to it).",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"append_to_note",description:"Append markdown text to the END of a note. Prefer this over update_note when adding content - it cannot damage existing content.",write:!0,confirmSummary:t=>n(e.cAppend,{id:t.note_id,n:String(t.text||"").length}),inputSchema:{type:"object",properties:{note_id:{type:"string"},text:{type:"string",description:"Markdown to append"}},required:["note_id","text"]}},{name:"tag_note",description:"Add a tag to a note (creates the tag if it does not exist).",write:!0,confirmSummary:t=>n(e.cTagNote,{id:t.note_id,tag:t.tag}),inputSchema:{type:"object",properties:{note_id:{type:"string"},tag:{type:"string",description:"Tag title"}},required:["note_id","tag"]}},{name:"untag_note",description:"Remove a tag from a note.",write:!0,confirmSummary:t=>n(e.cUntagNote,{id:t.note_id,tag:t.tag}),inputSchema:{type:"object",properties:{note_id:{type:"string"},tag:{type:"string",description:"Tag title"}},required:["note_id","tag"]}},{name:"set_todo_status",description:"Mark a to-do note as completed or not completed.",write:!0,confirmSummary:t=>n(e.cSetTodo,{id:t.note_id,state:t.completed?"done":"open"}),inputSchema:{type:"object",properties:{note_id:{type:"string"},completed:{type:"boolean"}},required:["note_id","completed"]}},{name:"update_notebook",description:"Rename a notebook and/or move it under another parent notebook.",write:!0,confirmSummary:t=>n(e.cUpdateNotebook,{id:t.notebook_id}),inputSchema:{type:"object",properties:{notebook_id:{type:"string"},title:{type:"string",description:"New title"},parent_id:{type:"string",description:'New parent notebook id ("" for top level)'}},required:["notebook_id"]}},{name:"ask_user",description:"Ask the user ONE multiple-choice question and wait for their answer. Renders clickable option buttons in the chat panel. Use this whenever you need the user to pick between alternatives before proceeding. Returns the chosen option text.",inputSchema:{type:"object",properties:{question:{type:"string",description:"The question to ask"},options:{type:"array",items:{type:"string"},description:"2-6 short option labels"}},required:["question","options"]}},{name:"approval_prompt",description:"INTERNAL: permission prompt bridge for the Claude Code permission system. Not for direct use.",inputSchema:{type:"object",properties:{tool_name:{type:"string"},input:{type:"object"},tool_use_id:{type:"string"}},required:["tool_name"]}},{name:"delete_note",description:"Delete a note (moves it to trash).",write:!0,confirmSummary:t=>n(e.cDeleteNote,{id:t.note_id}),inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}}],u={};let m=0;const g={};let h=0,y={};async function f(t,o){return!0===await joplin.settings.value("autoApproveAll")?(p({name:"toolDone",text:n(e.autoApproved,{s:t})}),!0):y[o]?(p({name:"toolDone",text:n(e.sessionApproved,{s:t})}),!0):new Promise(e=>{const i=String(++m),n=setTimeout(()=>{delete u[i],p({name:"confirmGone",requestId:i}),e(!1)},12e4);u[i]={resolve:e,timer:n,key:o,summary:t},p({name:"confirmWrite",requestId:i,summary:t})})}async function b(e,t,o={}){let i=[],n=1,s=!0;for(;s&&i.length<1e3;){const a=await joplin.data.get(e,{fields:t,page:n,limit:100,...o});i=i.concat(a.items),s=a.has_more,n++}return i}async function w(e,t){const o=await joplin.data.get(["search"],{query:e,type:"tag",fields:["id","title"]}),i=String(e).toLowerCase();return(o.items||[]).find(e=>String(e.title).toLowerCase()===i)||(t?await joplin.data.post(["tags"],null,{title:e}):null)}const _=s.createServer((t,o)=>{const i=(e,t)=>{const i=JSON.stringify(t);o.writeHead(e,{"Content-Type":"application/json"}),o.end(i)};if("GET"!==t.method||"/tools"!==t.url){if("POST"===t.method&&"/tool"===t.url){const o=[];return t.on("data",e=>o.push(e)),void t.on("end",async()=>{try{const t=JSON.parse(Buffer.concat(o).toString("utf8"));p({name:"toolUse",tool:t.name});const s=await async function(t,o){const i=c.find(e=>e.name===t);if(!i)return{result:"Unknown tool: "+t,isError:!0};if(i.write&&!1!==await joplin.settings.value("requireWriteConfirm")){const e=i.confirmSummary?i.confirmSummary(o):t;if(!await f(e,t))return{result:"The user DECLINED this operation. Do not retry it; ask the user what they would like instead.",isError:!0}}if("ask_user"===t){const e=Array.isArray(o.options)?o.options.map(e=>String(e)).slice(0,6):[];if(!o.question||e.length<2)return{result:"ask_user requires a question and at least 2 options.",isError:!0};const t=await function(e,t){return new Promise(o=>{const i="q"+String(++h),n=setTimeout(()=>{delete g[i],p({name:"questionGone",requestId:i}),o("")},3e5);g[i]={resolve:o,timer:n,question:e,options:t},p({name:"userQuestion",requestId:i,questions:[{question:e,options:t}]})})}(String(o.question),e);return t?(E("tool","ask_user: "+o.question+" -> "+t),{result:t}):{result:"The user did not answer within the time limit.",isError:!0}}if("approval_prompt"===t){let t="";try{const e=JSON.stringify(o.input||{});t=e.length>160?e.slice(0,160)+"...":e}catch(e){}const i=await f(n(e.cToolPermission,{name:String(o.tool_name||"?")})+(t&&"{}"!==t?" "+t:""),"tool:"+String(o.tool_name||"?"));return{result:JSON.stringify(i?{behavior:"allow",updatedInput:o.input||{}}:{behavior:"deny",message:"The user denied this tool use."})}}switch(t){case"list_notebooks":return{result:await b(["folders"],["id","title","parent_id"])};case"list_notes":{const e=await b(["folders",o.notebook_id,"notes"],["id","title","user_updated_time","is_todo","todo_completed"]);return e.sort((e,t)=>(t.user_updated_time||0)-(e.user_updated_time||0)),{result:e.slice(0,200).map(e=>({id:e.id,title:e.title,is_todo:e.is_todo,todo_completed:e.todo_completed}))}}case"search_notes":return{result:(await joplin.data.get(["search"],{query:o.query,fields:["id","title","parent_id","is_todo","todo_completed","todo_due","user_updated_time"],limit:50})).items};case"read_note":return{result:await joplin.data.get(["notes",o.note_id],{fields:["id","title","body","parent_id","is_todo","todo_completed","todo_due","user_created_time","user_updated_time","source_url"]})};case"get_selected_note":{const e=await joplin.workspace.selectedNote();return e?{result:{id:e.id,title:e.title,body:e.body,parent_id:e.parent_id}}:{result:"No note is currently selected.",isError:!0}}case"list_tags":return{result:await b(["tags"],["id","title"])};case"get_note_tags":return{result:(await joplin.data.get(["notes",o.note_id,"tags"],{fields:["id","title"],limit:100})).items};case"list_notes_by_tag":return{result:(await b(["tags",o.tag_id,"notes"],["id","title","parent_id","is_todo","todo_completed"])).slice(0,200)};case"list_note_attachments":{const e=((await joplin.data.get(["notes",o.note_id,"resources"],{fields:["id","title","mime","file_extension","size"],limit:100})).items||[]).map(e=>({id:e.id,title:e.title,mime:e.mime,size:e.size,local_path:r.join(r.resolve(S,"..","..","resources"),e.id+(e.file_extension?"."+e.file_extension:""))}));return{result:e.length?e:"This note has no attachments."}}case"open_note":return await joplin.commands.execute("openNote",o.note_id),{result:"Opened."};case"append_to_note":{const t=await joplin.data.get(["notes",o.note_id],{fields:["body"]}),i=String(t.body||"").replace(/\s+$/,"")+"\n\n"+String(o.text||"");return await joplin.data.put(["notes",o.note_id],null,{body:i}),p({name:"toolDone",text:n(e.dUpdated,{id:o.note_id})}),{result:"Appended."}}case"tag_note":{const e=await w(String(o.tag),!0);return await joplin.data.post(["tags",e.id,"notes"],null,{id:o.note_id}),{result:'Tagged with "'+e.title+'".'}}case"untag_note":{const e=await w(String(o.tag),!1);return e?(await joplin.data.delete(["tags",e.id,"notes",o.note_id]),{result:"Tag removed."}):{result:"Tag not found: "+o.tag,isError:!0}}case"set_todo_status":return(await joplin.data.get(["notes",o.note_id],{fields:["is_todo"]})).is_todo?(await joplin.data.put(["notes",o.note_id],null,{todo_completed:o.completed?Date.now():0}),{result:o.completed?"Marked as done.":"Marked as open."}):{result:"This note is not a to-do.",isError:!0};case"update_notebook":{const e={};return void 0!==o.title&&(e.title=o.title),void 0!==o.parent_id&&(e.parent_id=o.parent_id),0===Object.keys(e).length?{result:"Nothing to update.",isError:!0}:(await joplin.data.put(["folders",o.notebook_id],null,e),{result:"Notebook updated."})}case"create_note":{const t={title:o.title,body:o.body};if(o.notebook_id)t.parent_id=o.notebook_id;else{const e=await joplin.workspace.selectedFolder();e&&(t.parent_id=e.id)}const i=await joplin.data.post(["notes"],null,t);return p({name:"toolDone",text:n(e.dCreated,{title:i.title})}),{result:{id:i.id,title:i.title}}}case"update_note":{const t={};return void 0!==o.title&&(t.title=o.title),void 0!==o.body&&(t.body=o.body),void 0!==o.notebook_id&&(t.parent_id=o.notebook_id),0===Object.keys(t).length?{result:"Nothing to update.",isError:!0}:(await joplin.data.put(["notes",o.note_id],null,t),p({name:"toolDone",text:n(e.dUpdated,{id:o.note_id})}),{result:"Note updated."})}case"create_notebook":{const e={title:o.title};o.parent_id&&(e.parent_id=o.parent_id);const t=await joplin.data.post(["folders"],null,e);return{result:{id:t.id,title:t.title}}}case"delete_note":return await joplin.data.delete(["notes",o.note_id]),p({name:"toolDone",text:n(e.dDeleted,{id:o.note_id})}),{result:"Note deleted."}}return{result:"Unhandled tool: "+t,isError:!0}}(t.name,t.arguments||{});i(200,s)}catch(e){i(200,{result:"Tool execution failed: "+String(e&&e.message?e.message:e),isError:!0})}})}i(404,{error:"not found"})}else i(200,{tools:c.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))})});await new Promise(e=>_.listen(0,"127.0.0.1",()=>e()));const v=_.address().port,S=await joplin.plugins.dataDir(),j=r.join(S,"joplin-mcp-proxy.cjs");a.writeFileSync(j,"/* Joplin Aide - MCP stdio proxy (auto-generated, do not edit) */\n'use strict';\nvar http = require('http');\n\nvar PORT = parseInt(process.env.JOPLIN_AIDE_PORT || '0', 10);\n\nfunction callControl(method, path, payload, cb) {\n var data = payload ? Buffer.from(JSON.stringify(payload), 'utf8') : null;\n var req = http.request({\n host: '127.0.0.1',\n port: PORT,\n path: path,\n method: method,\n headers: data\n ? { 'Content-Type': 'application/json', 'Content-Length': data.length }\n : {},\n timeout: 180000,\n }, function (res) {\n var chunks = [];\n res.on('data', function (c) { chunks.push(c); });\n res.on('end', function () {\n try {\n cb(null, JSON.parse(Buffer.concat(chunks).toString('utf8')));\n } catch (e) {\n cb(e, null);\n }\n });\n });\n req.on('error', function (e) { cb(e, null); });\n req.on('timeout', function () { req.destroy(new Error('control server timeout')); });\n if (data) req.write(data);\n req.end();\n}\n\nfunction send(obj) {\n process.stdout.write(JSON.stringify(obj) + '\\n');\n}\n\nfunction reply(id, result) {\n send({ jsonrpc: '2.0', id: id, result: result });\n}\n\nfunction replyError(id, message) {\n send({ jsonrpc: '2.0', id: id, error: { code: -32000, message: String(message) } });\n}\n\nvar buffer = '';\nprocess.stdin.setEncoding('utf8');\nprocess.stdin.on('data', function (chunk) {\n buffer += chunk;\n var idx;\n while ((idx = buffer.indexOf('\\n')) >= 0) {\n var line = buffer.slice(0, idx).trim();\n buffer = buffer.slice(idx + 1);\n if (line) handleLine(line);\n }\n});\nprocess.stdin.on('end', function () { process.exit(0); });\n\nfunction handleLine(line) {\n var msg;\n try { msg = JSON.parse(line); } catch (e) { return; }\n var id = msg.id;\n var method = msg.method;\n\n if (method === 'initialize') {\n reply(id, {\n protocolVersion: (msg.params && msg.params.protocolVersion) || '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: 'joplin', version: '0.1.0' },\n });\n return;\n }\n if (method === 'ping') { reply(id, {}); return; }\n if (!method || method.indexOf('notifications/') === 0) return;\n\n if (method === 'tools/list') {\n callControl('GET', '/tools', null, function (err, res) {\n if (err) { replyError(id, err.message); return; }\n reply(id, { tools: res.tools || [] });\n });\n return;\n }\n\n if (method === 'tools/call') {\n var name = msg.params && msg.params.name;\n var args = (msg.params && msg.params.arguments) || {};\n callControl('POST', '/tool', { name: name, arguments: args }, function (err, res) {\n if (err) { replyError(id, err.message); return; }\n reply(id, {\n content: [{ type: 'text', text: typeof res.result === 'string' ? res.result : JSON.stringify(res.result) }],\n isError: !!res.isError,\n });\n });\n return;\n }\n\n if (id !== undefined) replyError(id, 'Method not supported: ' + method);\n}\n","utf8");const k=r.join(S,"mcp-config.json");a.writeFileSync(k,JSON.stringify({mcpServers:{joplin:{command:process.execPath,args:[j],env:{ELECTRON_RUN_AS_NODE:"1",JOPLIN_AIDE_PORT:String(v)}}}},null,2),"utf8");const x=r.join(S,"mcp-config-copilot.json");a.writeFileSync(x,JSON.stringify({mcpServers:{joplin:{type:"local",command:process.execPath,args:[j],env:{ELECTRON_RUN_AS_NODE:"1",JOPLIN_AIDE_PORT:String(v)},tools:["*"]}}},null,2),"utf8");const A=r.join(S,"conversations.json");let N=[];try{N=JSON.parse(a.readFileSync(A,"utf8")),Array.isArray(N)||(N=[])}catch(e){N=[]}let C=null,q=null;function T(){q&&clearTimeout(q),q=setTimeout(()=>{q=null;try{N.sort((e,t)=>(t.updated||0)-(e.updated||0)),N.length>100&&(N.length=100),a.writeFileSync(A,JSON.stringify(N),"utf8")}catch(e){console.error("Joplin Aide: failed to save history",e)}},400)}function E(e,t){C||(C={id:Date.now()+"-"+Math.random().toString(36).slice(2,8),title:"",sessionId:"",messages:[],updated:Date.now()},N.push(C)),"user"!==e||C.title||(C.title=t.slice(0,60)),C.messages.push({role:e,text:t}),C.updated=Date.now(),L&&(C.sessionId=L),T()}const P=r.join(S,"attachments");try{a.mkdirSync(P,{recursive:!0});const e=Date.now()-6048e5;for(const t of a.readdirSync(P)){const o=r.join(P,t);try{a.statSync(o).mtimeMs<e&&a.unlinkSync(o)}catch(e){}}}catch(e){}let I=[],D=0,O=null,L="",U="claude",R="";function J(){if(O)try{"win32"===process.platform?l.spawn("taskkill",["/pid",String(O.pid),"/T","/F"],{windowsHide:!0}):O.kill("SIGTERM")}catch(e){}}function F(e){return"win32"!==process.platform?e:/[\s"]/.test(e)?'"'+e.replace(/"/g,'\\"')+'"':e}function M(e){let t;try{t=JSON.parse(e)}catch(e){return}if(t.session_id&&(L=t.session_id),C&&L&&C.sessionId!==L&&(C.sessionId=L,T()),"stream_event"===t.type&&t.event){const e=t.event;return void("content_block_start"===e.type&&e.content_block&&"text"===e.content_block.type?p({name:"assistantStart"}):"content_block_delta"===e.type&&e.delta&&"text_delta"===e.delta.type&&e.delta.text&&p({name:"assistantDelta",text:e.delta.text}))}if("assistant"===t.type&&t.message&&Array.isArray(t.message.content)){for(const e of t.message.content)if("text"===e.type&&e.text)E("assistant",e.text),p({name:"assistantText",text:e.text});else if("tool_use"===e.type){const t=String(e.name||"").replace(/^mcp__joplin__/,"");"AskUserQuestion"===t&&e.input&&Array.isArray(e.input.questions)?(E("tool",t),p({name:"userQuestion",questions:e.input.questions})):(E("tool",t),p({name:"toolUse",tool:t}))}}else"result"===t.type&&(!0===t.is_error&&p({name:"error",text:("string"==typeof t.result&&t.result?t.result:t.subtype?String(t.subtype):"Request failed.").slice(0,500)}),p({name:"turnDone",isError:!0===t.is_error,costUsd:t.total_cost_usd}))}function B(e){let t;try{t=JSON.parse(e)}catch(e){return}const o=String(t.type||""),i=t.data||{};if("result"===o)return t.sessionId&&(L=String(t.sessionId),C&&C.sessionId!==L&&(C.sessionId=L,T())),0!==t.exitCode&&p({name:"error",text:"copilot finished with exit code "+t.exitCode}),void p({name:"turnDone",isError:0!==t.exitCode});if("assistant.message_start"!==o)if("assistant.message_delta"!==o)if("assistant.message"!==o){if(0===o.indexOf("tool.")){const e=String(i.toolName||i.name||i.tool||"");if(e&&/start|begin|request|call/i.test(o)){const t=e.replace(/^joplin[_\-]{1,2}/,"");E("tool",t),p({name:"toolUse",tool:t})}return}/\berror\b/i.test(o)&&p({name:"error",text:String(i.message||i.error||e).slice(0,500)})}else i.content&&(E("assistant",i.content),p({name:"assistantText",text:i.content}));else i.deltaContent&&p({name:"assistantDelta",text:i.deltaContent});else p({name:"assistantStart"})}await o(),await joplin.views.panels.onMessage(t,async t=>{if("ready"===t.name){await o(),C&&C.messages&&C.messages.length&&p({name:"conversationLoaded",messages:C.messages}),p({name:"busy",busy:!!O}),p({name:"backendState",backend:String(await joplin.settings.value("backend")||"claude")});for(const e of Object.keys(u))p({name:"confirmWrite",requestId:e,summary:u[e].summary});for(const e of Object.keys(g)){const t=g[e];p({name:"userQuestion",requestId:e,questions:[{question:t.question,options:t.options}]})}}else if("send"===t.name){const o=String(t.text||"").trim();o&&await async function(t){if(O)return p({name:"error",text:e.errAlreadyRunning}),void p({name:"busy",busy:!0});const o=String(await joplin.settings.value("backend")||"claude");R&&R!==o&&(L="",y={}),R=o;const i=String(await joplin.settings.value("copilot"===o?"copilotExtraArgs":"extraCliArgs")||"").trim();let s="";try{const e=await joplin.workspace.selectedNote();e&&(s=' The note currently open in the editor is "'+e.title+'" (id: '+e.id+").")}catch(e){}const a="copilot"===o?"joplin MCP":"mcp__joplin",r="You are embedded in the Joplin note-taking app as an assistant. Use the "+a+" tools to read, search, create and edit the user's notes and notebooks. Note bodies are Markdown. When editing a note, read it first and provide the full new body. Write operations may require user approval; if one is declined, do not retry it. To ask the user a multiple-choice question, use the "+a+" ask_user tool - it renders clickable buttons in the panel and waits for the answer. Other question mechanisms do NOT work in this environment; never use them."+s;let d,c;if("copilot"===o){d=String(await joplin.settings.value("copilotPath")||"").trim()||"copilot";const e=await joplin.settings.value("copilotModel");c=["--output-format","json","--no-color","--log-level","none","--disable-builtin-mcps","--no-ask-user","--additional-mcp-config",F("@"+x),"--allow-tool","joplin"],!0===await joplin.settings.value("autoApproveAll")&&c.push("--allow-all-tools");const t=String(await joplin.settings.value("copilotAllowTools")||"").split(",").map(e=>e.trim()).filter(e=>!!e);for(const e of t)c.push("--allow-tool",F(e));L&&c.push("--resume="+L),e&&c.push("--model",F(String(e))),i&&c.push(i)}else{d=String(await joplin.settings.value("claudePath")||"").trim()||"claude";const e=await joplin.settings.value("claudeModel"),t=String(await joplin.settings.value("extraAllowedTools")||"").split(",").map(e=>e.trim()).filter(e=>!!e),o=["mcp__joplin"].concat(t).join(",");c=["-p","--output-format","stream-json","--include-partial-messages","--verbose","--mcp-config",F(k),"--allowedTools",F(o),"--permission-prompt-tool","mcp__joplin__approval_prompt","--append-system-prompt",F(r)],L&&c.push("--resume",L),e&&c.push("--model",F(String(e))),i&&c.push(i)}if(I.length){if("copilot"===o){const e=/\.(png|jpe?g|gif|webp|bmp|pdf|docx|pptx|xlsx)$/i,o=[];let i=!1;for(const t of I)e.test(t.fileName)?c.push("--attachment",F(t.filePath)):(o.push("- "+t.filePath),i=!0);i&&(c.push("--add-dir",F(P)),t+="\n\n[The user attached the following files. Read them from disk:]\n"+o.join("\n"))}else t+="\n\n[The user attached the following files. Use the Read tool to view them:]\n"+I.map(e=>"- "+e.filePath).join("\n");I=[],p({name:"attachmentsCleared"})}E("user",t),"copilot"===o&&(t="<context>"+r+"</context>\n\n"+t),U=o,p({name:"busy",busy:!0});try{O=l.spawn(F(d),c,{shell:"win32"===process.platform,windowsHide:!0,stdio:["pipe","pipe","pipe"]})}catch(t){return p({name:"error",text:n(e.errStartFailed,{bin:d,err:String(t&&t.message?t.message:t)})}),p({name:"busy",busy:!1}),void(O=null)}O.stdin.write(t),O.stdin.end();let u="",m="";O.stdout.on("data",e=>{let t;for(u+=e.toString("utf8");(t=u.indexOf("\n"))>=0;){const e=u.slice(0,t).trim();u=u.slice(t+1),e&&("copilot"===U?B(e):M(e))}}),O.stderr.on("data",e=>{m+=e.toString("utf8")}),O.on("error",t=>{p({name:"error",text:n(e.errProcess,{bin:d,err:String(t&&t.message?t.message:t)})}),p({name:"busy",busy:!1}),O=null}),O.on("close",t=>{0!==t&&m.trim()&&p({name:"error",text:n(e.errExited,{bin:d,code:t,err:m.trim().slice(0,500)})}),p({name:"busy",busy:!1}),O=null})}(o)}else if("openUrl"===t.name){const e=String(t.url||"");if(/^https?:\/\//i.test(e))"win32"===process.platform?l.spawn("cmd",["/c","start","",e],{detached:!0,stdio:"ignore"}):"darwin"===process.platform?l.spawn("open",[e],{detached:!0,stdio:"ignore"}):l.spawn("xdg-open",[e],{detached:!0,stdio:"ignore"});else if(/^:\/[0-9a-f]{32}$/i.test(e))try{await joplin.commands.execute("openNote",e.slice(2))}catch(e){}}else if("toggleBackend"===t.name){const e="claude"===String(await joplin.settings.value("backend")||"claude")?"copilot":"claude";await joplin.settings.setValue("backend",e),p({name:"backendState",backend:e,switched:!0})}else if("stop"===t.name)J();else if("newSession"===t.name)L="",C=null,y={},J();else if("listHistory"===t.name)p({name:"historyList",items:N.slice().sort((e,t)=>(t.updated||0)-(e.updated||0)).map(e=>({id:e.id,title:e.title||"(empty)",updated:e.updated}))});else if("loadConversation"===t.name){const e=N.find(e=>e.id===t.id);e&&(J(),C=e,L=e.sessionId||"",p({name:"conversationLoaded",messages:e.messages||[]}))}else if("deleteConversation"===t.name)N=N.filter(e=>e.id!==t.id),C&&C.id===t.id&&(C=null,L=""),T(),p({name:"historyList",items:N.slice().sort((e,t)=>(t.updated||0)-(e.updated||0)).map(e=>({id:e.id,title:e.title||"(empty)",updated:e.updated}))});else if("attachFile"===t.name)try{const o=Buffer.from(String(t.data||""),"base64");if(o.length>8388608)return void p({name:"error",text:n(e.errAttachTooBig,{name:t.fileName})});const i=String(t.fileName||"file").replace(/[^\w.\-\u4e00-\u9fff\u3040-\u30ff]+/g,"_").slice(0,80),s="a"+ ++D+"-"+Date.now(),l=r.join(P,s+"-"+i);a.writeFileSync(l,o),I.push({id:s,fileName:i,filePath:l}),p({name:"attached",id:s,fileName:i})}catch(e){p({name:"error",text:String(e&&e.message?e.message:e)})}else if("removeAttachment"===t.name){const e=I.find(e=>e.id===t.id);if(e){try{a.unlinkSync(e.filePath)}catch(e){}I=I.filter(e=>e.id!==t.id)}p({name:"attachmentRemoved",id:t.id})}else if("questionAnswer"===t.name){const e=g[t.requestId];e&&(clearTimeout(e.timer),delete g[t.requestId],e.resolve(String(t.value||"")))}else if("confirmResult"===t.name){const e=u[t.requestId];e&&(clearTimeout(e.timer),delete u[t.requestId],!0===t.approved&&!0===t.always&&(y[e.key]=!0),e.resolve(!0===t.approved))}})}})})();
1
+ (()=>{"use strict";var e={317(e){e.exports=require("child_process")},896(e){e.exports=require("fs")},611(e){e.exports=require("http")},928(e){e.exports=require("path")}};const t={};function i(o){const n=t[o];if(void 0!==n)return n.exports;const s=t[o]={exports:{}};return e[o](s,s.exports,i),s.exports}const o={en_US:{inputPlaceholder:"Ask AI about your notes...",titleHistory:"History",titleNew:"New conversation",titleSend:"Send",titleStop:"Stop",titleDelete:"Delete",working:"⏳ Working...",noHistory:"No history yet",approve:"Approve",decline:"Decline",errAlreadyRunning:"A request is already running.",errStartFailed:"Failed to start {bin}: {err}",errProcess:"{bin} process error: {err}. Is the CLI installed and on PATH? (Settings > Joplin Aide)",errExited:"{bin} exited with code {code}: {err}",cCreateNote:'Create note "{title}"',cUpdateNote:"Update note {id}",cRetitle:' (retitle to "{title}")',cBodyChars:" [body: {n} chars]",cCreateNotebook:'Create notebook "{title}"',cDeleteNote:"DELETE note {id}",cToolPermission:"Tool permission: {name}",cAppend:"Append to note {id} [{n} chars]",cTagNote:'Tag note {id} with "{tag}"',cUntagNote:'Remove tag "{tag}" from note {id}',cSetTodo:"Mark to-do {id} as {state}",cUpdateNotebook:"Update notebook {id}",dCreated:"Created note: {title}",dUpdated:"Updated note {id}",dDeleted:"Deleted note {id}",autoApproved:"⚠ Auto-approved: {s}",alwaysAllow:"Always (this session)",titleAttach:"Attach files",titleBackend:"AI backend - click to switch (new session)",loadEarlier:"Show earlier messages ({n} more)",backendSwitched:"Switched to {name} - next message starts a new session",errAttachTooBig:"Attachment too large (max 8 MB): {name}",sessionApproved:"✓ Allowed by session rule: {s}"},zh_CN:{inputPlaceholder:"向 AI 询问你的笔记...",titleHistory:"历史会话",titleNew:"新会话",titleSend:"发送",titleStop:"停止",titleDelete:"删除",working:"⏳ 正在处理...",noHistory:"暂无历史会话",approve:"允许",decline:"拒绝",errAlreadyRunning:"已有请求正在运行。",errStartFailed:"启动 {bin} 失败:{err}",errProcess:"{bin} 进程错误:{err}。请确认已安装对应 CLI 且在 PATH 中(设置 > Joplin Aide)。",errExited:"{bin} 退出,代码 {code}:{err}",cCreateNote:"新建笔记「{title}」",cUpdateNote:"修改笔记 {id}",cRetitle:"(改名为「{title}」)",cBodyChars:"(正文 {n} 字符)",cCreateNotebook:"新建笔记本「{title}」",cDeleteNote:"删除笔记 {id}",cToolPermission:"工具权限请求:{name}",cAppend:"追加内容到笔记 {id}({n} 字符)",cTagNote:"给笔记 {id} 加标签「{tag}」",cUntagNote:"移除笔记 {id} 的标签「{tag}」",cSetTodo:"将待办 {id} 标记为{state}",cUpdateNotebook:"修改笔记本 {id}",dCreated:"已创建笔记:{title}",dUpdated:"已修改笔记 {id}",dDeleted:"已删除笔记 {id}",autoApproved:"⚠ 已自动批准:{s}",alwaysAllow:"本会话内一直允许",titleAttach:"添加附件",titleBackend:"AI 后端——点击切换(切换后开新会话)",loadEarlier:"显示更早的消息(还有 {n} 条)",backendSwitched:"已切换到 {name},下一条消息开始新会话",errAttachTooBig:"附件过大(上限 8 MB):{name}",sessionApproved:"✓ 按会话授权放行:{s}"},ja_JP:{inputPlaceholder:"ノートについて AI に質問...",titleHistory:"履歴",titleNew:"新しい会話",titleSend:"送信",titleStop:"停止",titleDelete:"削除",working:"⏳ 処理中...",noHistory:"履歴はまだありません",approve:"許可",decline:"拒否",errAlreadyRunning:"リクエストがすでに実行中です。",errStartFailed:"{bin} の起動に失敗しました:{err}",errProcess:"{bin} プロセスエラー:{err}。CLI がインストールされ PATH にあるか確認してください(設定 > Joplin Aide)。",errExited:"{bin} がコード {code} で終了:{err}",cCreateNote:"ノート「{title}」を作成",cUpdateNote:"ノート {id} を更新",cRetitle:"(「{title}」に改名)",cBodyChars:"(本文 {n} 文字)",cCreateNotebook:"ノートブック「{title}」を作成",cDeleteNote:"ノート {id} を削除",cToolPermission:"ツール権限リクエスト:{name}",cAppend:"ノート {id} に追記({n} 文字)",cTagNote:"ノート {id} にタグ「{tag}」を追加",cUntagNote:"ノート {id} からタグ「{tag}」を削除",cSetTodo:"タスク {id} を{state}にする",cUpdateNotebook:"ノートブック {id} を変更",dCreated:"ノートを作成:{title}",dUpdated:"ノート {id} を更新しました",dDeleted:"ノート {id} を削除しました",autoApproved:"⚠ 自動許可:{s}",alwaysAllow:"このセッションでは常に許可",titleAttach:"ファイルを添付",titleBackend:"AI バックエンド——クリックで切替(新しい会話を開始)",loadEarlier:"以前のメッセージを表示(残り {n} 件)",backendSwitched:"{name} に切り替えました。次のメッセージから新しいセッションになります",errAttachTooBig:"添付ファイルが大きすぎます(上限 8 MB):{name}",sessionApproved:"✓ セッション許可で実行:{s}"}};function n(e,t){return String(e).replace(/\{(\w+)\}/g,(e,i)=>void 0!==t[i]?String(t[i]):e)}const s=i(611),a=i(896),r=i(928),l=i(317);function d(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}joplin.plugins.register({onStart:async function(){const e=function(e){if(o[e])return o[e];const t=String(e||"").split("_")[0];return"zh"===t?o.zh_CN:"ja"===t?o.ja_JP:o.en_US}(await joplin.settings.globalValue("locale")||"en_US");await joplin.settings.registerSection("joplinAide",{label:"Joplin Aide",iconName:"fas fa-robot"}),await joplin.settings.registerSettings({backend:{section:"joplinAide",type:2,value:"claude",public:!0,isEnum:!0,options:{claude:"Claude Code",copilot:"GitHub Copilot"},label:"AI backend",description:"Which local CLI powers the chat. Each must be installed and logged in on its own (claude / copilot). Switching backends starts a new session."},requireWriteConfirm:{section:"joplinAide",type:3,value:!0,public:!0,label:"Confirm before the AI modifies notes",description:"Create/update/delete operations wait for your approval in the chat panel. Applies to both backends."},claudePath:{section:"joplinAide",type:2,value:"",public:!0,subType:"file_path",label:"— Claude · CLI path (claude)",description:'Full path to the claude executable. Leave empty to use "claude" from the system PATH.'},claudeModel:{section:"joplinAide",type:2,value:"",public:!0,label:"Claude · model (optional)",description:"Passed as --model. Leave empty to use the CLI default."},extraAllowedTools:{section:"joplinAide",type:2,value:"WebSearch,WebFetch,Read",public:!0,label:"Claude · additional allowed tools",description:"Comma-separated Claude Code tools to auto-allow besides the Joplin note tools. Tools NOT listed here trigger an Approve/Decline card in the chat panel. Default: WebSearch,WebFetch,Read (Read is needed to view chat and note attachments without prompting)."},extraCliArgs:{section:"joplinAide",type:2,value:"",public:!0,label:"Claude · extra CLI arguments",description:"Advanced: appended verbatim to the claude command line."},copilotPath:{section:"joplinAide",type:2,value:"",public:!0,subType:"file_path",label:"— Copilot · CLI path (copilot)",description:'Full path to the copilot executable. Leave empty to use "copilot" from the system PATH.'},copilotModel:{section:"joplinAide",type:2,value:"",public:!0,label:"Copilot · model (optional)",description:"Passed as --model to the Copilot CLI. Leave empty for automatic model selection."},copilotAllowTools:{section:"joplinAide",type:2,value:"url,write",public:!0,label:"Copilot · additional allowed tools",description:"Comma-separated permission patterns passed as --allow-tool (kinds: url(domain?), write, shell(cmd:*)). Copilot has no approval prompt: anything not listed is denied automatically. Default: url,write (web fetch + local file writes; note edits go through the Joplin tools and confirmation cards regardless). File reads need no entry."},copilotExtraArgs:{section:"joplinAide",type:2,value:"",public:!0,label:"Copilot · extra CLI arguments",description:"Advanced: appended verbatim to the copilot command line."},autoApproveAll:{section:"joplinAide",type:3,value:!1,public:!0,advanced:!0,label:"🔴 ⚠ AUTO MODE — approve ALL permission requests ⚠",description:"DANGER: when enabled, EVERY request is approved automatically without asking - note edits and deletions, AND any tool the AI asks for (potentially including shell commands). The AI gets free rein over your notes. Equivalent to running the CLI with permissions disabled. Leave OFF unless you fully accept the risk."}});const t=await joplin.views.panels.create("aideChatPanel");async function i(){try{const e=await joplin.workspace.selectedNote();c({name:"noteContext",title:e?e.title:""})}catch(e){}}function c(e){joplin.views.panels.postMessage(t,e)}await joplin.views.panels.addScript(t,"webview/panel.css"),await joplin.views.panels.addScript(t,"webview/markdown-it.min.js"),await joplin.views.panels.addScript(t,"webview/panel.js"),await joplin.views.panels.setHtml(t,['<div id="aide-root" data-i18n="'+d(JSON.stringify(e))+'">',' <div class="cc-header"><span class="cc-title">Aide</span>',' <span id="cc-note-context" class="cc-note-context"></span>',' <select id="cc-backend" title="'+d(e.titleBackend)+'">',' <option value="claude">Claude</option>',' <option value="copilot">Copilot</option>'," </select>",' <button id="cc-history" title="'+d(e.titleHistory)+'">&#x1F550;</button>',' <button id="cc-new" title="'+d(e.titleNew)+'">&#x2795;</button>'," </div>",' <div id="cc-messages"></div>',' <div id="cc-confirm"></div>',' <div id="cc-attachments"></div>',' <div class="cc-input-row">',' <textarea id="cc-input" rows="3" placeholder="'+d(e.inputPlaceholder)+'"></textarea>',' <div class="cc-input-buttons">',' <button id="cc-attach" title="'+d(e.titleAttach)+'">&#x1F4CE;</button>',' <input id="cc-file" type="file" multiple style="display:none;" />',' <button id="cc-send" title="'+d(e.titleSend)+'">&#x27A4;</button>',' <button id="cc-stop" title="'+d(e.titleStop)+'" style="display:none;">&#x25A0;</button>'," </div>"," </div>","</div>"].join("")),await joplin.views.panels.show(t,!1),await joplin.commands.register({name:"toggleAidePanel",label:"Toggle Aide panel",iconName:"fas fa-robot",execute:async()=>{const e=await joplin.views.panels.visible(t);await joplin.views.panels.show(t,!e)}}),await joplin.views.toolbarButtons.create("aidePanelButton","toggleAidePanel","noteToolbar"),await joplin.workspace.onNoteSelectionChange(i);const p=[{name:"list_notebooks",description:"List all notebooks (folders) with id, title and parent_id.",inputSchema:{type:"object",properties:{},required:[]}},{name:"list_notes",description:"List notes in a notebook (id + title, most recently updated first, max 200).",inputSchema:{type:"object",properties:{notebook_id:{type:"string",description:"Notebook id"}},required:["notebook_id"]}},{name:"search_notes",description:'Search notes with Joplin query syntax: plain words, "exact phrase", tag:xxx, notebook:xxx, type:todo, iscompleted:0, created:20260101, updated:day-7, sourceurl:*. Returns id, title, parent_id, todo fields (max 50).',inputSchema:{type:"object",properties:{query:{type:"string",description:"Search query"}},required:["query"]}},{name:"read_note",description:"Read a note: title, markdown body, notebook id.",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"get_selected_note",description:'Get the note currently open in the Joplin editor (id, title, body). Use this when the user says "this note".',inputSchema:{type:"object",properties:{},required:[]}},{name:"create_note",description:"Create a new note in a notebook.",write:!0,confirmSummary:t=>n(e.cCreateNote,{title:t.title||"(untitled)"}),inputSchema:{type:"object",properties:{title:{type:"string"},body:{type:"string",description:"Markdown body"},notebook_id:{type:"string",description:"Target notebook id (optional; defaults to the currently selected notebook)"}},required:["title","body"]}},{name:"update_note",description:"Update an existing note. Only the provided fields are changed. To edit content, read the note first, then send the FULL new body.",write:!0,confirmSummary:t=>n(e.cUpdateNote,{id:t.note_id})+(t.title?n(e.cRetitle,{title:t.title}):"")+(void 0!==t.body?n(e.cBodyChars,{n:String(t.body).length}):""),inputSchema:{type:"object",properties:{note_id:{type:"string"},title:{type:"string"},body:{type:"string",description:"Full replacement markdown body"},notebook_id:{type:"string",description:"Move to this notebook"}},required:["note_id"]}},{name:"create_notebook",description:"Create a new notebook, optionally under a parent notebook.",write:!0,confirmSummary:t=>n(e.cCreateNotebook,{title:t.title||""}),inputSchema:{type:"object",properties:{title:{type:"string"},parent_id:{type:"string",description:"Parent notebook id (optional)"}},required:["title"]}},{name:"list_tags",description:"List all tags (id + title).",inputSchema:{type:"object",properties:{},required:[]}},{name:"get_note_tags",description:"List the tags attached to a note.",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"list_notes_by_tag",description:"List notes that carry a tag (by tag id, see list_tags).",inputSchema:{type:"object",properties:{tag_id:{type:"string"}},required:["tag_id"]}},{name:"list_note_attachments",description:"List the file attachments (resources) of a note, with their LOCAL file paths. Use the Read tool with local_path to view an attachment (images, PDFs, text...).",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"open_note",description:"Open a note in the Joplin editor (navigate the user to it).",inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}},{name:"append_to_note",description:"Append markdown text to the END of a note. Prefer this over update_note when adding content - it cannot damage existing content.",write:!0,confirmSummary:t=>n(e.cAppend,{id:t.note_id,n:String(t.text||"").length}),inputSchema:{type:"object",properties:{note_id:{type:"string"},text:{type:"string",description:"Markdown to append"}},required:["note_id","text"]}},{name:"tag_note",description:"Add a tag to a note (creates the tag if it does not exist).",write:!0,confirmSummary:t=>n(e.cTagNote,{id:t.note_id,tag:t.tag}),inputSchema:{type:"object",properties:{note_id:{type:"string"},tag:{type:"string",description:"Tag title"}},required:["note_id","tag"]}},{name:"untag_note",description:"Remove a tag from a note.",write:!0,confirmSummary:t=>n(e.cUntagNote,{id:t.note_id,tag:t.tag}),inputSchema:{type:"object",properties:{note_id:{type:"string"},tag:{type:"string",description:"Tag title"}},required:["note_id","tag"]}},{name:"set_todo_status",description:"Mark a to-do note as completed or not completed.",write:!0,confirmSummary:t=>n(e.cSetTodo,{id:t.note_id,state:t.completed?"done":"open"}),inputSchema:{type:"object",properties:{note_id:{type:"string"},completed:{type:"boolean"}},required:["note_id","completed"]}},{name:"update_notebook",description:"Rename a notebook and/or move it under another parent notebook.",write:!0,confirmSummary:t=>n(e.cUpdateNotebook,{id:t.notebook_id}),inputSchema:{type:"object",properties:{notebook_id:{type:"string"},title:{type:"string",description:"New title"},parent_id:{type:"string",description:'New parent notebook id ("" for top level)'}},required:["notebook_id"]}},{name:"ask_user",description:"Ask the user ONE multiple-choice question and wait for their answer. Renders clickable option buttons in the chat panel. Use this whenever you need the user to pick between alternatives before proceeding. Returns the chosen option text.",inputSchema:{type:"object",properties:{question:{type:"string",description:"The question to ask"},options:{type:"array",items:{type:"string"},description:"2-6 short option labels"}},required:["question","options"]}},{name:"approval_prompt",description:"INTERNAL: permission prompt bridge for the Claude Code permission system. Not for direct use.",inputSchema:{type:"object",properties:{tool_name:{type:"string"},input:{type:"object"},tool_use_id:{type:"string"}},required:["tool_name"]}},{name:"delete_note",description:"Delete a note (moves it to trash).",write:!0,confirmSummary:t=>n(e.cDeleteNote,{id:t.note_id}),inputSchema:{type:"object",properties:{note_id:{type:"string"}},required:["note_id"]}}],u={};let m=0;const g={};let h=0,y={};async function f(t,i){return!0===await joplin.settings.value("autoApproveAll")?(c({name:"toolDone",text:n(e.autoApproved,{s:t})}),!0):y[i]?(c({name:"toolDone",text:n(e.sessionApproved,{s:t})}),!0):new Promise(e=>{const o=String(++m),n=setTimeout(()=>{delete u[o],c({name:"confirmGone",requestId:o}),e(!1)},12e4);u[o]={resolve:e,timer:n,key:i,summary:t},c({name:"confirmWrite",requestId:o,summary:t})})}async function w(e,t,i={}){let o=[],n=1,s=!0;for(;s&&o.length<1e3;){const a=await joplin.data.get(e,{fields:t,page:n,limit:100,...i});o=o.concat(a.items),s=a.has_more,n++}return o}async function b(e,t){const i=await joplin.data.get(["search"],{query:e,type:"tag",fields:["id","title"]}),o=String(e).toLowerCase();return(i.items||[]).find(e=>String(e.title).toLowerCase()===o)||(t?await joplin.data.post(["tags"],null,{title:e}):null)}const _=s.createServer((t,i)=>{const o=(e,t)=>{const o=JSON.stringify(t);i.writeHead(e,{"Content-Type":"application/json"}),i.end(o)};if("GET"!==t.method||"/tools"!==t.url){if("POST"===t.method&&"/tool"===t.url){const i=[];return t.on("data",e=>i.push(e)),void t.on("end",async()=>{try{const t=JSON.parse(Buffer.concat(i).toString("utf8"));c({name:"toolUse",tool:t.name});const s=await async function(t,i){const o=p.find(e=>e.name===t);if(!o)return{result:"Unknown tool: "+t,isError:!0};if(o.write&&!1!==await joplin.settings.value("requireWriteConfirm")){const e=o.confirmSummary?o.confirmSummary(i):t;if(!await f(e,t))return{result:"The user DECLINED this operation. Do not retry it; ask the user what they would like instead.",isError:!0}}if("ask_user"===t){const e=Array.isArray(i.options)?i.options.map(e=>String(e)).slice(0,6):[];if(!i.question||e.length<2)return{result:"ask_user requires a question and at least 2 options.",isError:!0};const t=await function(e,t){return new Promise(i=>{const o="q"+String(++h),n=setTimeout(()=>{delete g[o],c({name:"questionGone",requestId:o}),i("")},3e5);g[o]={resolve:i,timer:n,question:e,options:t},c({name:"userQuestion",requestId:o,questions:[{question:e,options:t}]})})}(String(i.question),e);return t?(L("tool","ask_user: "+i.question+" -> "+t),{result:t}):{result:"The user did not answer within the time limit.",isError:!0}}if("approval_prompt"===t){let t="";try{const e=JSON.stringify(i.input||{});t=e.length>160?e.slice(0,160)+"...":e}catch(e){}const o=await f(n(e.cToolPermission,{name:String(i.tool_name||"?")})+(t&&"{}"!==t?" "+t:""),"tool:"+String(i.tool_name||"?"));return{result:JSON.stringify(o?{behavior:"allow",updatedInput:i.input||{}}:{behavior:"deny",message:"The user denied this tool use."})}}switch(t){case"list_notebooks":return{result:await w(["folders"],["id","title","parent_id"])};case"list_notes":{const e=await w(["folders",i.notebook_id,"notes"],["id","title","user_updated_time","is_todo","todo_completed"]);return e.sort((e,t)=>(t.user_updated_time||0)-(e.user_updated_time||0)),{result:e.slice(0,200).map(e=>({id:e.id,title:e.title,is_todo:e.is_todo,todo_completed:e.todo_completed}))}}case"search_notes":return{result:(await joplin.data.get(["search"],{query:i.query,fields:["id","title","parent_id","is_todo","todo_completed","todo_due","user_updated_time"],limit:50})).items};case"read_note":return{result:await joplin.data.get(["notes",i.note_id],{fields:["id","title","body","parent_id","is_todo","todo_completed","todo_due","user_created_time","user_updated_time","source_url"]})};case"get_selected_note":{const e=await joplin.workspace.selectedNote();return e?{result:{id:e.id,title:e.title,body:e.body,parent_id:e.parent_id}}:{result:"No note is currently selected.",isError:!0}}case"list_tags":return{result:await w(["tags"],["id","title"])};case"get_note_tags":return{result:(await joplin.data.get(["notes",i.note_id,"tags"],{fields:["id","title"],limit:100})).items};case"list_notes_by_tag":return{result:(await w(["tags",i.tag_id,"notes"],["id","title","parent_id","is_todo","todo_completed"])).slice(0,200)};case"list_note_attachments":{const e=((await joplin.data.get(["notes",i.note_id,"resources"],{fields:["id","title","mime","file_extension","size"],limit:100})).items||[]).map(e=>({id:e.id,title:e.title,mime:e.mime,size:e.size,local_path:r.join(r.resolve(S,"..","..","resources"),e.id+(e.file_extension?"."+e.file_extension:""))}));return{result:e.length?e:"This note has no attachments."}}case"open_note":return await joplin.commands.execute("openNote",i.note_id),{result:"Opened."};case"append_to_note":{const t=await joplin.data.get(["notes",i.note_id],{fields:["body"]}),o=String(t.body||"").replace(/\s+$/,"")+"\n\n"+String(i.text||"");return await joplin.data.put(["notes",i.note_id],null,{body:o}),c({name:"toolDone",text:n(e.dUpdated,{id:i.note_id})}),{result:"Appended."}}case"tag_note":{const e=await b(String(i.tag),!0);return await joplin.data.post(["tags",e.id,"notes"],null,{id:i.note_id}),{result:'Tagged with "'+e.title+'".'}}case"untag_note":{const e=await b(String(i.tag),!1);return e?(await joplin.data.delete(["tags",e.id,"notes",i.note_id]),{result:"Tag removed."}):{result:"Tag not found: "+i.tag,isError:!0}}case"set_todo_status":return(await joplin.data.get(["notes",i.note_id],{fields:["is_todo"]})).is_todo?(await joplin.data.put(["notes",i.note_id],null,{todo_completed:i.completed?Date.now():0}),{result:i.completed?"Marked as done.":"Marked as open."}):{result:"This note is not a to-do.",isError:!0};case"update_notebook":{const e={};return void 0!==i.title&&(e.title=i.title),void 0!==i.parent_id&&(e.parent_id=i.parent_id),0===Object.keys(e).length?{result:"Nothing to update.",isError:!0}:(await joplin.data.put(["folders",i.notebook_id],null,e),{result:"Notebook updated."})}case"create_note":{const t={title:i.title,body:i.body};if(i.notebook_id)t.parent_id=i.notebook_id;else{const e=await joplin.workspace.selectedFolder();e&&(t.parent_id=e.id)}const o=await joplin.data.post(["notes"],null,t);return c({name:"toolDone",text:n(e.dCreated,{title:o.title})}),{result:{id:o.id,title:o.title}}}case"update_note":{const t={};return void 0!==i.title&&(t.title=i.title),void 0!==i.body&&(t.body=i.body),void 0!==i.notebook_id&&(t.parent_id=i.notebook_id),0===Object.keys(t).length?{result:"Nothing to update.",isError:!0}:(await joplin.data.put(["notes",i.note_id],null,t),c({name:"toolDone",text:n(e.dUpdated,{id:i.note_id})}),{result:"Note updated."})}case"create_notebook":{const e={title:i.title};i.parent_id&&(e.parent_id=i.parent_id);const t=await joplin.data.post(["folders"],null,e);return{result:{id:t.id,title:t.title}}}case"delete_note":return await joplin.data.delete(["notes",i.note_id]),c({name:"toolDone",text:n(e.dDeleted,{id:i.note_id})}),{result:"Note deleted."}}return{result:"Unhandled tool: "+t,isError:!0}}(t.name,t.arguments||{});o(200,s)}catch(e){o(200,{result:"Tool execution failed: "+String(e&&e.message?e.message:e),isError:!0})}})}o(404,{error:"not found"})}else o(200,{tools:p.map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))})});await new Promise(e=>_.listen(0,"127.0.0.1",()=>e()));const v=_.address().port,S=await joplin.plugins.dataDir(),j=r.join(S,"joplin-mcp-proxy.cjs");a.writeFileSync(j,"/* Joplin Aide - MCP stdio proxy (auto-generated, do not edit) */\n'use strict';\nvar http = require('http');\n\nvar PORT = parseInt(process.env.JOPLIN_AIDE_PORT || '0', 10);\n\nfunction callControl(method, path, payload, cb) {\n var data = payload ? Buffer.from(JSON.stringify(payload), 'utf8') : null;\n var req = http.request({\n host: '127.0.0.1',\n port: PORT,\n path: path,\n method: method,\n headers: data\n ? { 'Content-Type': 'application/json', 'Content-Length': data.length }\n : {},\n timeout: 180000,\n }, function (res) {\n var chunks = [];\n res.on('data', function (c) { chunks.push(c); });\n res.on('end', function () {\n try {\n cb(null, JSON.parse(Buffer.concat(chunks).toString('utf8')));\n } catch (e) {\n cb(e, null);\n }\n });\n });\n req.on('error', function (e) { cb(e, null); });\n req.on('timeout', function () { req.destroy(new Error('control server timeout')); });\n if (data) req.write(data);\n req.end();\n}\n\nfunction send(obj) {\n process.stdout.write(JSON.stringify(obj) + '\\n');\n}\n\nfunction reply(id, result) {\n send({ jsonrpc: '2.0', id: id, result: result });\n}\n\nfunction replyError(id, message) {\n send({ jsonrpc: '2.0', id: id, error: { code: -32000, message: String(message) } });\n}\n\nvar buffer = '';\nprocess.stdin.setEncoding('utf8');\nprocess.stdin.on('data', function (chunk) {\n buffer += chunk;\n var idx;\n while ((idx = buffer.indexOf('\\n')) >= 0) {\n var line = buffer.slice(0, idx).trim();\n buffer = buffer.slice(idx + 1);\n if (line) handleLine(line);\n }\n});\nprocess.stdin.on('end', function () { process.exit(0); });\n\nfunction handleLine(line) {\n var msg;\n try { msg = JSON.parse(line); } catch (e) { return; }\n var id = msg.id;\n var method = msg.method;\n\n if (method === 'initialize') {\n reply(id, {\n protocolVersion: (msg.params && msg.params.protocolVersion) || '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: 'joplin', version: '0.1.0' },\n });\n return;\n }\n if (method === 'ping') { reply(id, {}); return; }\n if (!method || method.indexOf('notifications/') === 0) return;\n\n if (method === 'tools/list') {\n callControl('GET', '/tools', null, function (err, res) {\n if (err) { replyError(id, err.message); return; }\n reply(id, { tools: res.tools || [] });\n });\n return;\n }\n\n if (method === 'tools/call') {\n var name = msg.params && msg.params.name;\n var args = (msg.params && msg.params.arguments) || {};\n callControl('POST', '/tool', { name: name, arguments: args }, function (err, res) {\n if (err) { replyError(id, err.message); return; }\n reply(id, {\n content: [{ type: 'text', text: typeof res.result === 'string' ? res.result : JSON.stringify(res.result) }],\n isError: !!res.isError,\n });\n });\n return;\n }\n\n if (id !== undefined) replyError(id, 'Method not supported: ' + method);\n}\n","utf8");const k=r.join(S,"mcp-config.json");a.writeFileSync(k,JSON.stringify({mcpServers:{joplin:{command:process.execPath,args:[j],env:{ELECTRON_RUN_AS_NODE:"1",JOPLIN_AIDE_PORT:String(v)}}}},null,2),"utf8");const A=r.join(S,"mcp-config-copilot.json");a.writeFileSync(A,JSON.stringify({mcpServers:{joplin:{type:"local",command:process.execPath,args:[j],env:{ELECTRON_RUN_AS_NODE:"1",JOPLIN_AIDE_PORT:String(v)},tools:["*"]}}},null,2),"utf8");const x=r.join(S,"conversations.json");let N=[];try{N=JSON.parse(a.readFileSync(x,"utf8")),Array.isArray(N)||(N=[])}catch(e){N=[]}let C=null,q=null;const T=200,E=r.join(S,"history");try{a.mkdirSync(E,{recursive:!0})}catch(e){}function P(e,t){return r.join(E,String(e).replace(/[^\w-]/g,"_")+"-"+t+".json")}let I=!1,O=!1;function D(){q&&clearTimeout(q),q=setTimeout(()=>{q=null,async function(){if(I)O=!0;else{I=!0;try{do{O=!1,N.sort((e,t)=>(t.updated||0)-(e.updated||0)),N.length>100&&(N.length=100),await a.promises.writeFile(x,JSON.stringify(N),"utf8")}while(O)}catch(e){console.error("Joplin Aide: failed to save history",e)}finally{I=!1}}}()},400)}function L(e,t){if(C||(C={id:Date.now()+"-"+Math.random().toString(36).slice(2,8),title:"",sessionId:"",messages:[],updated:Date.now()},N.push(C)),"user"!==e||C.title||(C.title=t.slice(0,60)),C.messages.push({role:e,text:t}),C.messages.length>2*T){const e=C.messages.splice(0,T),t=C.archiveSegments||0;try{a.writeFileSync(P(C.id,t),JSON.stringify(e),"utf8"),C.archiveSegments=t+1}catch(t){C.messages.unshift(...e),console.error("Joplin Aide: failed to write history segment",t)}}C.updated=Date.now(),M&&(C.sessionId=M),D()}const U=r.join(S,"attachments");try{a.mkdirSync(U,{recursive:!0});const e=Date.now()-6048e5;for(const t of a.readdirSync(U)){const i=r.join(U,t);try{a.statSync(i).mtimeMs<e&&a.unlinkSync(i)}catch(e){}}}catch(e){}let R=[],J=0,F=null,M="",B="claude",H="";function W(){if(F)try{"win32"===process.platform?l.spawn("taskkill",["/pid",String(F.pid),"/T","/F"],{windowsHide:!0}):F.kill("SIGTERM")}catch(e){}}function G(e){return"win32"!==process.platform?e:/[\s"]/.test(e)?'"'+e.replace(/"/g,'\\"')+'"':e}function z(e){let t;try{t=JSON.parse(e)}catch(e){return}if(t.session_id&&(M=t.session_id),C&&M&&C.sessionId!==M&&(C.sessionId=M,D()),"stream_event"===t.type&&t.event){const e=t.event;return void("content_block_start"===e.type&&e.content_block&&"text"===e.content_block.type?c({name:"assistantStart"}):"content_block_delta"===e.type&&e.delta&&"text_delta"===e.delta.type&&e.delta.text&&c({name:"assistantDelta",text:e.delta.text}))}if("assistant"===t.type&&t.message&&Array.isArray(t.message.content)){for(const e of t.message.content)if("text"===e.type&&e.text)L("assistant",e.text),c({name:"assistantText",text:e.text});else if("tool_use"===e.type){const t=String(e.name||"").replace(/^mcp__joplin__/,"");"AskUserQuestion"===t&&e.input&&Array.isArray(e.input.questions)?(L("tool",t),c({name:"userQuestion",questions:e.input.questions})):(L("tool",t),c({name:"toolUse",tool:t}))}}else"result"===t.type&&(!0===t.is_error&&c({name:"error",text:("string"==typeof t.result&&t.result?t.result:t.subtype?String(t.subtype):"Request failed.").slice(0,500)}),c({name:"turnDone",isError:!0===t.is_error,costUsd:t.total_cost_usd}))}function V(e){let t;try{t=JSON.parse(e)}catch(e){return}const i=String(t.type||""),o=t.data||{};if("result"===i)return t.sessionId&&(M=String(t.sessionId),C&&C.sessionId!==M&&(C.sessionId=M,D())),0!==t.exitCode&&c({name:"error",text:"copilot finished with exit code "+t.exitCode}),void c({name:"turnDone",isError:0!==t.exitCode});if("assistant.message_start"!==i)if("assistant.message_delta"!==i)if("assistant.message"!==i){if(0===i.indexOf("tool.")){const e=String(o.toolName||o.name||o.tool||"");if(e&&/start|begin|request|call/i.test(i)){const t=e.replace(/^joplin[_\-]{1,2}/,"");L("tool",t),c({name:"toolUse",tool:t})}return}/\berror\b/i.test(i)&&c({name:"error",text:String(o.message||o.error||e).slice(0,500)})}else o.content&&(L("assistant",o.content),c({name:"assistantText",text:o.content}));else o.deltaContent&&c({name:"assistantDelta",text:o.deltaContent});else c({name:"assistantStart"})}await i(),await joplin.views.panels.onMessage(t,async t=>{if("ready"===t.name){await i(),C&&C.messages&&C.messages.length&&c({name:"conversationLoaded",id:C.id,messages:C.messages,archiveSegments:C.archiveSegments||0}),c({name:"busy",busy:!!F}),c({name:"backendState",backend:String(await joplin.settings.value("backend")||"claude")});for(const e of Object.keys(u))c({name:"confirmWrite",requestId:e,summary:u[e].summary});for(const e of Object.keys(g)){const t=g[e];c({name:"userQuestion",requestId:e,questions:[{question:t.question,options:t.options}]})}}else if("send"===t.name){const i=String(t.text||"").trim();i&&await async function(t){if(F)return c({name:"error",text:e.errAlreadyRunning}),void c({name:"busy",busy:!0});const i=String(await joplin.settings.value("backend")||"claude");H&&H!==i&&(M="",y={}),H=i;const o=String(await joplin.settings.value("copilot"===i?"copilotExtraArgs":"extraCliArgs")||"").trim();let s="";try{const e=await joplin.workspace.selectedNote();e&&(s=' The note currently open in the editor is "'+e.title+'" (id: '+e.id+").")}catch(e){}const a="copilot"===i?"joplin MCP":"mcp__joplin",r="You are embedded in the Joplin note-taking app as an assistant. Use the "+a+" tools to read, search, create and edit the user's notes and notebooks. Note bodies are Markdown. When editing a note, read it first and provide the full new body. Write operations may require user approval; if one is declined, do not retry it. To ask the user a multiple-choice question, use the "+a+" ask_user tool - it renders clickable buttons in the panel and waits for the answer. Other question mechanisms do NOT work in this environment; never use them."+s;let d,p;if("copilot"===i){d=String(await joplin.settings.value("copilotPath")||"").trim()||"copilot";const e=await joplin.settings.value("copilotModel");p=["--output-format","json","--no-color","--log-level","none","--disable-builtin-mcps","--no-ask-user","--additional-mcp-config",G("@"+A),"--allow-tool","joplin"],!0===await joplin.settings.value("autoApproveAll")&&p.push("--allow-all-tools");const t=String(await joplin.settings.value("copilotAllowTools")||"").split(",").map(e=>e.trim()).filter(e=>!!e);for(const e of t)p.push("--allow-tool",G(e));M&&p.push("--resume="+M),e&&p.push("--model",G(String(e))),o&&p.push(o)}else{d=String(await joplin.settings.value("claudePath")||"").trim()||"claude";const e=await joplin.settings.value("claudeModel"),t=String(await joplin.settings.value("extraAllowedTools")||"").split(",").map(e=>e.trim()).filter(e=>!!e),i=["mcp__joplin"].concat(t).join(",");p=["-p","--output-format","stream-json","--include-partial-messages","--verbose","--mcp-config",G(k),"--allowedTools",G(i),"--permission-prompt-tool","mcp__joplin__approval_prompt","--append-system-prompt",G(r)],M&&p.push("--resume",M),e&&p.push("--model",G(String(e))),o&&p.push(o)}if(R.length){if("copilot"===i){const e=/\.(png|jpe?g|gif|webp|bmp|pdf|docx|pptx|xlsx)$/i,i=[];let o=!1;for(const t of R)e.test(t.fileName)?p.push("--attachment",G(t.filePath)):(i.push("- "+t.filePath),o=!0);o&&(p.push("--add-dir",G(U)),t+="\n\n[The user attached the following files. Read them from disk:]\n"+i.join("\n"))}else t+="\n\n[The user attached the following files. Use the Read tool to view them:]\n"+R.map(e=>"- "+e.filePath).join("\n");R=[],c({name:"attachmentsCleared"})}L("user",t),"copilot"===i&&(t="<context>"+r+"</context>\n\n"+t),B=i,c({name:"busy",busy:!0});try{F=l.spawn(G(d),p,{shell:"win32"===process.platform,windowsHide:!0,stdio:["pipe","pipe","pipe"]})}catch(t){return c({name:"error",text:n(e.errStartFailed,{bin:d,err:String(t&&t.message?t.message:t)})}),c({name:"busy",busy:!1}),void(F=null)}F.stdin.write(t),F.stdin.end();let u="",m="";F.stdout.on("data",e=>{let t;for(u+=e.toString("utf8");(t=u.indexOf("\n"))>=0;){const e=u.slice(0,t).trim();u=u.slice(t+1),e&&("copilot"===B?V(e):z(e))}}),F.stderr.on("data",e=>{m+=e.toString("utf8")}),F.on("error",t=>{c({name:"error",text:n(e.errProcess,{bin:d,err:String(t&&t.message?t.message:t)})}),c({name:"busy",busy:!1}),F=null}),F.on("close",t=>{0!==t&&m.trim()&&c({name:"error",text:n(e.errExited,{bin:d,code:t,err:m.trim().slice(0,500)})}),c({name:"busy",busy:!1}),F=null})}(i)}else if("openUrl"===t.name){const e=String(t.url||"");if(/^https?:\/\//i.test(e))"win32"===process.platform?l.spawn("cmd",["/c","start","",e],{detached:!0,stdio:"ignore"}):"darwin"===process.platform?l.spawn("open",[e],{detached:!0,stdio:"ignore"}):l.spawn("xdg-open",[e],{detached:!0,stdio:"ignore"});else if(/^:\/[0-9a-f]{32}$/i.test(e))try{await joplin.commands.execute("openNote",e.slice(2))}catch(e){}}else if("setBackend"===t.name){const e="copilot"===t.value?"copilot":"claude";e!==String(await joplin.settings.value("backend")||"claude")&&(await joplin.settings.setValue("backend",e),c({name:"backendState",backend:e,switched:!0}))}else if("stop"===t.name)W();else if("newSession"===t.name)M="",C=null,y={},W();else if("listHistory"===t.name)c({name:"historyList",items:N.slice().sort((e,t)=>(t.updated||0)-(e.updated||0)).map(e=>({id:e.id,title:e.title||"(empty)",updated:e.updated}))});else if("loadConversation"===t.name){const e=N.find(e=>e.id===t.id);e&&(W(),C=e,M=e.sessionId||"",c({name:"conversationLoaded",id:e.id,messages:e.messages||[],archiveSegments:e.archiveSegments||0}))}else if("loadOlder"===t.name){let e=[];try{const i=JSON.parse(a.readFileSync(P(String(t.id),0|Number(t.seq)),"utf8"));Array.isArray(i)&&(e=i)}catch(e){}c({name:"olderMessages",id:t.id,seq:t.seq,messages:e})}else if("deleteConversation"===t.name){const e=N.find(e=>e.id===t.id);if(e&&e.archiveSegments)for(let t=0;t<e.archiveSegments;t++)try{a.unlinkSync(P(e.id,t))}catch(e){}N=N.filter(e=>e.id!==t.id),C&&C.id===t.id&&(C=null,M=""),D(),c({name:"historyList",items:N.slice().sort((e,t)=>(t.updated||0)-(e.updated||0)).map(e=>({id:e.id,title:e.title||"(empty)",updated:e.updated}))})}else if("attachFile"===t.name)try{const i=Buffer.from(String(t.data||""),"base64");if(i.length>8388608)return void c({name:"error",text:n(e.errAttachTooBig,{name:t.fileName})});const o=String(t.fileName||"file").replace(/[^\w.\-\u4e00-\u9fff\u3040-\u30ff]+/g,"_").slice(0,80),s="a"+ ++J+"-"+Date.now(),l=r.join(U,s+"-"+o);a.writeFileSync(l,i),R.push({id:s,fileName:o,filePath:l}),c({name:"attached",id:s,fileName:o})}catch(e){c({name:"error",text:String(e&&e.message?e.message:e)})}else if("removeAttachment"===t.name){const e=R.find(e=>e.id===t.id);if(e){try{a.unlinkSync(e.filePath)}catch(e){}R=R.filter(e=>e.id!==t.id)}c({name:"attachmentRemoved",id:t.id})}else if("questionAnswer"===t.name){const e=g[t.requestId];e&&(clearTimeout(e.timer),delete g[t.requestId],e.resolve(String(t.value||"")))}else if("confirmResult"===t.name){const e=u[t.requestId];e&&(clearTimeout(e.timer),delete u[t.requestId],!0===t.approved&&!0===t.always&&(y[e.key]=!0),e.resolve(!0===t.approved))}})}})})();
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 1,
3
3
  "id": "com.github.joplin-aide",
4
4
  "app_min_version": "2.8",
5
- "version": "1.0.2",
5
+ "version": "1.1.0",
6
6
  "name": "Joplin Aide",
7
7
  "description": "AI assistant chat panel. Ask about your notes and let the AI read, search, create and edit them via your local Claude Code or GitHub Copilot CLI. Write operations require your confirmation.",
8
8
  "author": "lim0513",
Binary file
@@ -42,13 +42,23 @@
42
42
  color: inherit;
43
43
  }
44
44
  .cc-header button + button { margin-left: 2px; }
45
- /* Backend switcher: a small pill showing the active engine */
45
+ /* Backend switcher: a small pill dropdown showing the active engine */
46
46
  #cc-backend {
47
+ appearance: none;
48
+ -webkit-appearance: none;
47
49
  border: 1px solid rgba(127, 127, 127, 0.4);
48
50
  border-radius: 999px;
49
51
  font-size: 11px;
50
52
  padding: 2px 9px;
53
+ margin: 0 2px;
51
54
  color: var(--joplin-color-faded, #999);
55
+ background: transparent;
56
+ cursor: pointer;
57
+ }
58
+ #cc-backend:hover { color: inherit; background: rgba(127, 127, 127, 0.15); }
59
+ #cc-backend option {
60
+ color: var(--joplin-color, #ddd);
61
+ background: var(--joplin-background-color, #1e1e1e);
52
62
  }
53
63
  #cc-backend.cc-backend-copilot {
54
64
  border-color: rgba(88, 166, 255, 0.55);
@@ -1 +1 @@
1
- function postMsg(e){webviewApi.postMessage(e)}var _busy=!1;function el(e){return document.getElementById(e)}function T(e){if(!window._i18n){var t=document.getElementById("aide-root");if(t&&t.dataset.i18n)try{window._i18n=JSON.parse(t.dataset.i18n)}catch(e){window._i18n={}}else window._i18n={}}return window._i18n[e]||e}function escapeHtml(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}var _md=null;function getMd(){return _md||("function"==typeof window.markdownit&&(_md=window.markdownit({html:!1,linkify:!0,breaks:!0})),_md)}function renderLite(e){var t=getMd();if(t)return t.render(String(e??""));var a=escapeHtml(e);return(a=(a=(a=(a=a.replace(/```([\s\S]*?)```/g,function(e,t){return'<pre class="cc-code">'+t.replace(/^\n/,"")+"</pre>"})).replace(/`([^`\n]+)`/g,"<code>$1</code>")).replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>")).replace(/\n/g,"<br>")).replace(/(<\/pre>)<br>/g,"$1")}function scrollToBottom(){var e=el("cc-messages");e&&(e.scrollTop=e.scrollHeight)}function appendToMessages(e){var t=el("cc-messages");if(t){var a=document.getElementById("cc-spinner");a&&a.parentElement===t?t.insertBefore(e,a):t.appendChild(e)}}function addBubble(e,t){if(!el("cc-messages"))return null;var a=document.createElement("div");return a.className="cc-msg "+e,a.innerHTML=t,appendToMessages(a),scrollToBottom(),a}function addToolChip(e){var t=el("cc-messages");if(t){var a=t.lastElementChild;if(a&&"cc-spinner"===a.id&&a.previousElementSibling&&(a=a.previousElementSibling),a&&a.classList.contains("cc-tools")){var s=document.createElement("span");s.className="cc-tool-chip",s.textContent=e,a.appendChild(s)}else{var n=document.createElement("div");n.className="cc-tools";var c=document.createElement("span");c.className="cc-tool-chip",c.textContent=e,n.appendChild(c),appendToMessages(n)}scrollToBottom()}}function setBusy(e){_busy=e;var t=el("cc-send"),a=el("cc-stop"),s=el("cc-input");t&&(t.style.display=e?"none":""),a&&(a.style.display=e?"":"none"),s&&(s.disabled=!1);var n=el("cc-messages"),c=document.getElementById("cc-spinner");if(e){if(!c&&n){var r=document.createElement("div");r.id="cc-spinner",r.className="cc-spinner",r.textContent=T("working"),n.appendChild(r),scrollToBottom()}}else c&&c.remove()}function sendText(e){_busy||(e=String(e||"").trim())&&(setBusy(!0),addBubble("cc-user",escapeHtml(e).replace(/\n/g,"<br>")),postMsg({name:"send",text:e}))}function sendCurrent(){if(!_busy){var e=el("cc-input");if(e){var t=e.value.trim();if(t){setBusy(!0),e.value="";var a=escapeHtml(t).replace(/\n/g,"<br>"),s=document.querySelectorAll("#cc-attachments .cc-att-chip");if(s.length){for(var n=[],c=0;c<s.length;c++)n.push(escapeHtml(s[c].dataset.name||""));a+='<div class="cc-msg-atts">📎 '+n.join(" · ")+"</div>"}addBubble("cc-user",a),postMsg({name:"send",text:t})}}}}function sendFileToBackend(e){if(e)if(e.size>8388608)addBubble("cc-error",escapeHtml(e.name)+" (>8MB)");else{var t=new FileReader;t.onload=function(){var a=String(t.result||""),s=a.indexOf(",");postMsg({name:"attachFile",fileName:e.name,data:s>=0?a.slice(s+1):a})},t.readAsDataURL(e)}}function handleFiles(e){for(var t=0;t<e.length&&t<5;t++)sendFileToBackend(e[t])}document.addEventListener("paste",function(e){if(e.clipboardData){for(var t=[],a=e.clipboardData.items||[],s=0;s<a.length;s++)if("file"===a[s].kind){var n=a[s].getAsFile();n&&t.push(n)}if(t.length){e.preventDefault();for(var c=0;c<t.length&&c<5;c++){var r=t[c];if(!r.name||"image.png"===r.name){var l=r.type&&r.type.indexOf("/")>=0?r.type.split("/")[1]:"png";r=new File([r],"paste-"+Date.now()+"-"+c+"."+l,{type:r.type||"image/png"})}sendFileToBackend(r)}}}}),document.addEventListener("dragover",function(e){e.preventDefault()}),document.addEventListener("drop",function(e){e.preventDefault(),e.dataTransfer&&e.dataTransfer.files&&e.dataTransfer.files.length&&handleFiles(e.dataTransfer.files)}),document.addEventListener("change",function(e){e.target&&"cc-file"===e.target.id&&e.target.files&&(handleFiles(e.target.files),e.target.value="")}),document.addEventListener("click",function(e){var t=e.target;if(t){var a=t.closest?t.closest(".cc-msg a[href]"):null;if(a)return e.preventDefault(),void postMsg({name:"openUrl",url:a.getAttribute("href")});if("cc-send"!==t.id)if("cc-backend"!==t.id)if("cc-attach"!==t.id){var s=t.closest?t.closest(".cc-q-option"):null;if(s){var n=s.closest(".cc-question-card");if(n){for(var c=n.querySelectorAll(".cc-q-option"),r=0;r<c.length;r++)c[r].disabled=!0;n.classList.add("cc-q-answered"),s.classList.add("cc-q-chosen")}var l=n?n.dataset.requestId:"";l?postMsg({name:"questionAnswer",requestId:l,value:s.dataset.value}):sendText(s.dataset.value)}else{var i=t.closest?t.closest(".cc-att-del"):null;if(i)postMsg({name:"removeAttachment",id:i.dataset.id});else if("cc-stop"!==t.id){if("cc-history"===t.id){var o=el("cc-history-overlay");return o?void o.remove():void postMsg({name:"listHistory"})}var d=t.closest?t.closest(".cc-hist-del"):null;if(d)postMsg({name:"deleteConversation",id:d.dataset.id});else{var m=t.closest?t.closest(".cc-hist-item"):null;if(m)postMsg({name:"loadConversation",id:m.dataset.id});else if("cc-history-overlay"!==t.id)if("cc-new"!==t.id){var u=t.closest?t.closest(".cc-confirm-btn"):null;if(u){var p=u.closest(".cc-confirm-card");return postMsg({name:"confirmResult",requestId:u.dataset.requestId,approved:"1"===u.dataset.approved,always:"1"===u.dataset.always}),void(p&&p.remove())}}else{postMsg({name:"newSession"});var v=el("cc-messages");v&&(v.innerHTML="");var f=el("cc-confirm");f&&(f.innerHTML="")}else t.remove()}}else postMsg({name:"stop"})}}else{var g=el("cc-file");g&&g.click()}else postMsg({name:"toggleBackend"});else sendCurrent()}}),document.addEventListener("keydown",function(e){e.target&&"cc-input"===e.target.id&&"Enter"===e.key&&!e.shiftKey&&(e.preventDefault(),sendCurrent())});var _streamBubble=null,_streamRaw="";function endStreamBubble(){_streamBubble=null,_streamRaw=""}webviewApi.onMessage(function(e){if(e&&e.message){var t=e.message;if("assistantStart"===t.name)return _streamRaw="",void(_streamBubble=addBubble("cc-assistant",""));if("assistantDelta"===t.name)return _streamBubble||(_streamRaw="",_streamBubble=addBubble("cc-assistant","")),_streamRaw+=t.text,_streamBubble.innerHTML=renderLite(_streamRaw),void scrollToBottom();if("historyList"===t.name){var a=el("cc-history-overlay");a&&a.remove();var s=document.createElement("div");s.id="cc-history-overlay";var n=document.createElement("div");if(n.className="cc-hist-box",t.items&&t.items.length)for(var c=0;c<t.items.length;c++){var r=t.items[c],l=r.updated?new Date(r.updated):null,i=l?l.getFullYear()+"-"+("0"+(l.getMonth()+1)).slice(-2)+"-"+("0"+l.getDate()).slice(-2)+" "+("0"+l.getHours()).slice(-2)+":"+("0"+l.getMinutes()).slice(-2):"",o=document.createElement("div");o.className="cc-hist-item",o.dataset.id=r.id,o.innerHTML='<div class="cc-hist-main"><div class="cc-hist-title">'+escapeHtml(r.title)+'</div><div class="cc-hist-date">'+i+'</div></div><button class="cc-hist-del" data-id="'+r.id+'" title="'+escapeHtml(T("titleDelete"))+'">&#x2715;</button>',n.appendChild(o)}else n.innerHTML='<div class="cc-hist-empty">'+escapeHtml(T("noHistory"))+"</div>";s.appendChild(n),document.getElementById("aide-root").appendChild(s)}else if("conversationLoaded"===t.name){var d=el("cc-history-overlay");d&&d.remove();var m=el("cc-messages");m&&(m.innerHTML="");var u=el("cc-confirm");u&&(u.innerHTML="");for(var p=t.messages||[],v=0;v<p.length;v++){var f=p[v];"user"===f.role?addBubble("cc-user",escapeHtml(f.text).replace(/\n/g,"<br>")):"assistant"===f.role?addBubble("cc-assistant",renderLite(f.text)):"tool"===f.role?addToolChip("⚙ "+f.text):addBubble("cc-error",escapeHtml(f.text))}setBusy(!1)}else if("attached"===t.name){var g=el("cc-attachments");if(g){var b=document.createElement("span");b.className="cc-att-chip",b.dataset.id=t.id,b.dataset.name=t.fileName,b.innerHTML="📎 "+escapeHtml(t.fileName)+' <button class="cc-att-del" data-id="'+t.id+'">✕</button>',g.appendChild(b)}}else if("attachmentRemoved"===t.name){var h=document.querySelector('.cc-att-chip[data-id="'+t.id+'"]');h&&h.remove()}else if("attachmentsCleared"===t.name){var y=el("cc-attachments");y&&(y.innerHTML="")}else if("backendState"===t.name){var B=el("cc-backend"),w="copilot"===t.backend?"Copilot":"Claude";if(B&&(B.textContent=w,B.classList.toggle("cc-backend-copilot","copilot"===t.backend)),t.switched){var q=document.createElement("div");q.className="cc-switch-note",q.textContent="⇄ "+T("backendSwitched").replace("{name}",w),appendToMessages(q),scrollToBottom()}}else if("noteContext"===t.name){var M=el("cc-note-context");M&&(M.textContent=t.title?"📄 "+t.title:"")}else if("assistantText"===t.name)_streamBubble?(_streamBubble.innerHTML=renderLite(t.text),endStreamBubble(),scrollToBottom()):addBubble("cc-assistant",renderLite(t.text));else if("userQuestion"===t.name){endStreamBubble();for(var H=t.questions||[],L=0;L<H.length;L++){var C=H[L];(D=document.createElement("div")).className="cc-question-card",t.requestId&&(D.dataset.requestId=t.requestId);for(var x='<div class="cc-q-text">'+escapeHtml(C.question||"")+'</div><div class="cc-q-options">',E=C.options||[],_=0;_<E.length;_++)x+='<button class="cc-q-option" data-value="'+escapeHtml(w="string"==typeof E[_]?E[_]:E[_].label||"")+'">'+escapeHtml(w)+"</button>";x+="</div>",D.innerHTML=x,appendToMessages(D)}scrollToBottom()}else if("questionGone"===t.name){var S=document.querySelector('.cc-question-card[data-request-id="'+t.requestId+'"]');if(S){for(var k=S.querySelectorAll(".cc-q-option"),I=0;I<k.length;I++)k[I].disabled=!0;S.classList.add("cc-q-answered")}}else if("toolUse"===t.name)endStreamBubble(),addToolChip("⚙ "+t.tool);else if("toolDone"===t.name)addToolChip("✔ "+t.text);else if("busy"===t.name)setBusy(!0===t.busy);else if("turnDone"===t.name)endStreamBubble(),setBusy(!1);else if("error"===t.name)endStreamBubble(),addBubble("cc-error",escapeHtml(t.text)),setBusy(!1);else if("confirmWrite"===t.name){var D,N=el("cc-confirm");if(!N)return;(D=document.createElement("div")).className="cc-confirm-card",D.innerHTML='<div class="cc-confirm-text">⚠ '+escapeHtml(t.summary)+'</div><div class="cc-confirm-actions"><button class="cc-confirm-btn cc-approve" data-request-id="'+t.requestId+'" data-approved="1">'+escapeHtml(T("approve"))+'</button><button class="cc-confirm-btn cc-always" data-request-id="'+t.requestId+'" data-approved="1" data-always="1">'+escapeHtml(T("alwaysAllow"))+'</button><button class="cc-confirm-btn cc-decline" data-request-id="'+t.requestId+'" data-approved="0">'+escapeHtml(T("decline"))+"</button></div>",N.appendChild(D),scrollToBottom()}else if("confirmGone"===t.name){var A=document.querySelectorAll('.cc-confirm-btn[data-request-id="'+t.requestId+'"]');if(A.length){var F=A[0].closest(".cc-confirm-card");F&&F.remove()}}}}),postMsg({name:"ready"});
1
+ function postMsg(e){webviewApi.postMessage(e)}var _busy=!1;function el(e){return document.getElementById(e)}function T(e){if(!window._i18n){var t=document.getElementById("aide-root");if(t&&t.dataset.i18n)try{window._i18n=JSON.parse(t.dataset.i18n)}catch(e){window._i18n={}}else window._i18n={}}return window._i18n[e]||e}function escapeHtml(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}var _md=null;function getMd(){return _md||("function"==typeof window.markdownit&&(_md=window.markdownit({html:!1,linkify:!0,breaks:!0})),_md)}function renderLite(e){var t=getMd();if(t)return t.render(String(e??""));var s=escapeHtml(e);return(s=(s=(s=(s=s.replace(/```([\s\S]*?)```/g,function(e,t){return'<pre class="cc-code">'+t.replace(/^\n/,"")+"</pre>"})).replace(/`([^`\n]+)`/g,"<code>$1</code>")).replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>")).replace(/\n/g,"<br>")).replace(/(<\/pre>)<br>/g,"$1")}function scrollToBottom(){var e=el("cc-messages");e&&(e.scrollTop=e.scrollHeight)}function appendToMessages(e){var t=el("cc-messages");if(t){var s=document.getElementById("cc-spinner");s&&s.parentElement===t?t.insertBefore(e,s):t.appendChild(e)}}function addBubble(e,t){if(!el("cc-messages"))return null;var s=document.createElement("div");return s.className="cc-msg "+e,s.innerHTML=t,appendToMessages(s),scrollToBottom(),s}function addToolChip(e){var t=el("cc-messages");if(t){var s=t.lastElementChild;if(s&&"cc-spinner"===s.id&&s.previousElementSibling&&(s=s.previousElementSibling),s&&s.classList.contains("cc-tools")){var a=document.createElement("span");a.className="cc-tool-chip",a.textContent=e,s.appendChild(a)}else{var n=document.createElement("div");n.className="cc-tools";var c=document.createElement("span");c.className="cc-tool-chip",c.textContent=e,n.appendChild(c),appendToMessages(n)}scrollToBottom()}}function setBusy(e){_busy=e;var t=el("cc-send"),s=el("cc-stop"),a=el("cc-input");t&&(t.style.display=e?"none":""),s&&(s.style.display=e?"":"none"),a&&(a.disabled=!1);var n=el("cc-messages"),c=document.getElementById("cc-spinner");if(e){if(!c&&n){var i=document.createElement("div");i.id="cc-spinner",i.className="cc-spinner",i.textContent=T("working"),n.appendChild(i),scrollToBottom()}}else c&&c.remove()}function sendText(e){_busy||(e=String(e||"").trim())&&(setBusy(!0),addBubble("cc-user",escapeHtml(e).replace(/\n/g,"<br>")),postMsg({name:"send",text:e}))}function sendCurrent(){if(!_busy){var e=el("cc-input");if(e){var t=e.value.trim();if(t){setBusy(!0),e.value="";var s=escapeHtml(t).replace(/\n/g,"<br>"),a=document.querySelectorAll("#cc-attachments .cc-att-chip");if(a.length){for(var n=[],c=0;c<a.length;c++)n.push(escapeHtml(a[c].dataset.name||""));s+='<div class="cc-msg-atts">📎 '+n.join(" · ")+"</div>"}addBubble("cc-user",s),postMsg({name:"send",text:t})}}}}function sendFileToBackend(e){if(e)if(e.size>8388608)addBubble("cc-error",escapeHtml(e.name)+" (>8MB)");else{var t=new FileReader;t.onload=function(){var s=String(t.result||""),a=s.indexOf(",");postMsg({name:"attachFile",fileName:e.name,data:a>=0?s.slice(a+1):s})},t.readAsDataURL(e)}}function handleFiles(e){for(var t=0;t<e.length&&t<5;t++)sendFileToBackend(e[t])}document.addEventListener("paste",function(e){if(e.clipboardData){for(var t=[],s=e.clipboardData.items||[],a=0;a<s.length;a++)if("file"===s[a].kind){var n=s[a].getAsFile();n&&t.push(n)}if(t.length){e.preventDefault();for(var c=0;c<t.length&&c<5;c++){var i=t[c];if(!i.name||"image.png"===i.name){var r=i.type&&i.type.indexOf("/")>=0?i.type.split("/")[1]:"png";i=new File([i],"paste-"+Date.now()+"-"+c+"."+r,{type:i.type||"image/png"})}sendFileToBackend(i)}}}}),document.addEventListener("dragover",function(e){e.preventDefault()}),document.addEventListener("drop",function(e){e.preventDefault(),e.dataTransfer&&e.dataTransfer.files&&e.dataTransfer.files.length&&handleFiles(e.dataTransfer.files)}),document.addEventListener("change",function(e){e.target&&"cc-file"===e.target.id&&e.target.files&&(handleFiles(e.target.files),e.target.value=""),e.target&&"cc-backend"===e.target.id&&postMsg({name:"setBackend",value:e.target.value})}),document.addEventListener("click",function(e){var t=e.target;if(t){var s=t.closest?t.closest(".cc-msg a[href]"):null;if(s)return e.preventDefault(),void postMsg({name:"openUrl",url:s.getAttribute("href")});if("cc-send"!==t.id)if("cc-attach"!==t.id){var a=t.closest?t.closest(".cc-q-option"):null;if(a){var n=a.closest(".cc-question-card");if(n){for(var c=n.querySelectorAll(".cc-q-option"),i=0;i<c.length;i++)c[i].disabled=!0;n.classList.add("cc-q-answered"),a.classList.add("cc-q-chosen")}var r=n?n.dataset.requestId:"";r?postMsg({name:"questionAnswer",requestId:r,value:a.dataset.value}):sendText(a.dataset.value)}else{var l=t.closest?t.closest(".cc-att-del"):null;if(l)postMsg({name:"removeAttachment",id:l.dataset.id});else if("cc-stop"!==t.id){if("cc-history"===t.id){var o=el("cc-history-overlay");return o?void o.remove():void postMsg({name:"listHistory"})}var d=t.closest?t.closest(".cc-hist-del"):null;if(d)postMsg({name:"deleteConversation",id:d.dataset.id});else{var m=t.closest?t.closest(".cc-hist-item"):null;if(m)postMsg({name:"loadConversation",id:m.dataset.id});else if("cc-history-overlay"!==t.id)if("cc-new"!==t.id){var u=t.closest?t.closest(".cc-confirm-btn"):null;if(u){var p=u.closest(".cc-confirm-card");return postMsg({name:"confirmResult",requestId:u.dataset.requestId,approved:"1"===u.dataset.approved,always:"1"===u.dataset.always}),void(p&&p.remove())}}else{postMsg({name:"newSession"}),_histMsgs=null,_histShown=0,_histConvId="",_histSegNext=-1,_histFetching=!1;var v=el("cc-messages");v&&(v.innerHTML="");var f=el("cc-confirm");f&&(f.innerHTML="")}else t.remove()}}else postMsg({name:"stop"})}}else{var g=el("cc-file");g&&g.click()}else sendCurrent()}}),document.addEventListener("keydown",function(e){e.target&&"cc-input"===e.target.id&&"Enter"===e.key&&!e.shiftKey&&(e.preventDefault(),sendCurrent())});var _histMsgs=null,_histShown=0,_histConvId="",_histSegNext=-1,_histFetching=!1;function buildMsgNode(e){var t=document.createElement("div");if("user"===e.role)t.className="cc-msg cc-user",t.innerHTML=escapeHtml(e.text).replace(/\n/g,"<br>");else if("assistant"===e.role)t.className="cc-msg cc-assistant",t.innerHTML=renderLite(e.text);else if("tool"===e.role){t.className="cc-tools";var s=document.createElement("span");s.className="cc-tool-chip",s.textContent="⚙ "+e.text,t.appendChild(s)}else t.className="cc-msg cc-error",t.innerHTML=escapeHtml(e.text);return t}function renderHistoryChunk(){var e=el("cc-messages");if(e&&_histMsgs){var t=_histMsgs.length-_histShown,s=Math.max(0,t-100);if(!(t<=s)){for(var a=document.createDocumentFragment(),n=s;n<t;n++)a.appendChild(buildMsgNode(_histMsgs[n]));e.insertBefore(a,e.firstChild),_histShown+=t-s}}}function maybeLoadEarlier(){var e=el("cc-messages");if(e)if(_histMsgs&&_histShown<_histMsgs.length){var t=e.scrollHeight;renderHistoryChunk(),e.scrollTop+=e.scrollHeight-t}else _histSegNext>=0&&!_histFetching&&(_histFetching=!0,postMsg({name:"loadOlder",id:_histConvId,seq:_histSegNext}))}document.addEventListener("scroll",function(e){e.target&&"cc-messages"===e.target.id&&e.target.scrollTop<40&&maybeLoadEarlier()},!0);var _streamBubble=null,_streamRaw="";function endStreamBubble(){_streamBubble=null,_streamRaw=""}webviewApi.onMessage(function(e){if(e&&e.message){var t=e.message;if("assistantStart"===t.name)return _streamRaw="",void(_streamBubble=addBubble("cc-assistant",""));if("assistantDelta"===t.name)return _streamBubble||(_streamRaw="",_streamBubble=addBubble("cc-assistant","")),_streamRaw+=t.text,_streamBubble.innerHTML=renderLite(_streamRaw),void scrollToBottom();if("historyList"===t.name){var s=el("cc-history-overlay");s&&s.remove();var a=document.createElement("div");a.id="cc-history-overlay";var n=document.createElement("div");if(n.className="cc-hist-box",t.items&&t.items.length)for(var c=0;c<t.items.length;c++){var i=t.items[c],r=i.updated?new Date(i.updated):null,l=r?r.getFullYear()+"-"+("0"+(r.getMonth()+1)).slice(-2)+"-"+("0"+r.getDate()).slice(-2)+" "+("0"+r.getHours()).slice(-2)+":"+("0"+r.getMinutes()).slice(-2):"",o=document.createElement("div");o.className="cc-hist-item",o.dataset.id=i.id,o.innerHTML='<div class="cc-hist-main"><div class="cc-hist-title">'+escapeHtml(i.title)+'</div><div class="cc-hist-date">'+l+'</div></div><button class="cc-hist-del" data-id="'+i.id+'" title="'+escapeHtml(T("titleDelete"))+'">&#x2715;</button>',n.appendChild(o)}else n.innerHTML='<div class="cc-hist-empty">'+escapeHtml(T("noHistory"))+"</div>";a.appendChild(n),document.getElementById("aide-root").appendChild(a)}else if("conversationLoaded"===t.name){var d=el("cc-history-overlay");d&&d.remove();var m=el("cc-messages");m&&(m.innerHTML="");var u=el("cc-confirm");u&&(u.innerHTML=""),_histMsgs=t.messages||[],_histShown=0,_histConvId=t.id||"",_histSegNext=(t.archiveSegments||0)-1,_histFetching=!1,renderHistoryChunk(),scrollToBottom(),setBusy(!1)}else if("attached"===t.name){var p=el("cc-attachments");if(p){var v=document.createElement("span");v.className="cc-att-chip",v.dataset.id=t.id,v.dataset.name=t.fileName,v.innerHTML="📎 "+escapeHtml(t.fileName)+' <button class="cc-att-del" data-id="'+t.id+'">✕</button>',p.appendChild(v)}}else if("attachmentRemoved"===t.name){var f=document.querySelector('.cc-att-chip[data-id="'+t.id+'"]');f&&f.remove()}else if("attachmentsCleared"===t.name){var g=el("cc-attachments");g&&(g.innerHTML="")}else if("olderMessages"===t.name){if(_histFetching=!1,t.id!==_histConvId)return;if(t.messages&&t.messages.length){for(var h=el("cc-messages"),b=h?h.scrollHeight:0,y=document.createDocumentFragment(),_=0;_<t.messages.length;_++)y.appendChild(buildMsgNode(t.messages[_]));h&&(h.insertBefore(y,h.firstChild),h.scrollTop+=h.scrollHeight-b),_histSegNext=("number"==typeof t.seq?t.seq:_histSegNext)-1}else _histSegNext=-1}else if("backendState"===t.name){var M=el("cc-backend"),w="copilot"===t.backend?"Copilot":"Claude";if(M&&(M.value="copilot"===t.backend?"copilot":"claude",M.classList.toggle("cc-backend-copilot","copilot"===t.backend)),t.switched){var B=document.createElement("div");B.className="cc-switch-note",B.textContent="⇄ "+T("backendSwitched").replace("{name}",w),appendToMessages(B),scrollToBottom()}}else if("noteContext"===t.name){var H=el("cc-note-context");H&&(H.textContent=t.title?"📄 "+t.title:"")}else if("assistantText"===t.name)_streamBubble?(_streamBubble.innerHTML=renderLite(t.text),endStreamBubble(),scrollToBottom()):addBubble("cc-assistant",renderLite(t.text));else if("userQuestion"===t.name){endStreamBubble();for(var q=t.questions||[],C=0;C<q.length;C++){var S=q[C];(F=document.createElement("div")).className="cc-question-card",t.requestId&&(F.dataset.requestId=t.requestId);for(var L='<div class="cc-q-text">'+escapeHtml(S.question||"")+'</div><div class="cc-q-options">',x=S.options||[],E=0;E<x.length;E++)L+='<button class="cc-q-option" data-value="'+escapeHtml(w="string"==typeof x[E]?x[E]:x[E].label||"")+'">'+escapeHtml(w)+"</button>";L+="</div>",F.innerHTML=L,appendToMessages(F)}scrollToBottom()}else if("questionGone"===t.name){var N=document.querySelector('.cc-question-card[data-request-id="'+t.requestId+'"]');if(N){for(var k=N.querySelectorAll(".cc-q-option"),I=0;I<k.length;I++)k[I].disabled=!0;N.classList.add("cc-q-answered")}}else if("toolUse"===t.name)endStreamBubble(),addToolChip("⚙ "+t.tool);else if("toolDone"===t.name)addToolChip("✔ "+t.text);else if("busy"===t.name)setBusy(!0===t.busy);else if("turnDone"===t.name)endStreamBubble(),setBusy(!1);else if("error"===t.name)endStreamBubble(),addBubble("cc-error",escapeHtml(t.text)),setBusy(!1);else if("confirmWrite"===t.name){var F,D=el("cc-confirm");if(!D)return;(F=document.createElement("div")).className="cc-confirm-card",F.innerHTML='<div class="cc-confirm-text">⚠ '+escapeHtml(t.summary)+'</div><div class="cc-confirm-actions"><button class="cc-confirm-btn cc-approve" data-request-id="'+t.requestId+'" data-approved="1">'+escapeHtml(T("approve"))+'</button><button class="cc-confirm-btn cc-always" data-request-id="'+t.requestId+'" data-approved="1" data-always="1">'+escapeHtml(T("alwaysAllow"))+'</button><button class="cc-confirm-btn cc-decline" data-request-id="'+t.requestId+'" data-approved="0">'+escapeHtml(T("decline"))+"</button></div>",D.appendChild(F),scrollToBottom()}else if("confirmGone"===t.name){var A=document.querySelectorAll('.cc-confirm-btn[data-request-id="'+t.requestId+'"]');if(A.length){var R=A[0].closest(".cc-confirm-card");R&&R.remove()}}}}),postMsg({name:"ready"});