@agentproto/apps 0.4.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mail-triage/agents/triager.ts","../src/mail-triage/workflows/triage-inbox.ts","../src/mail-triage/index.ts"],"names":[],"mappings":";;;;;;;;;AAIO,IAAM,OAAA,GAAsB;AAAA,EACjC,OAAO,WAAA,CAAY;AAAA,IACjB,MAAA,EAAQ,UAAA;AAAA,IACR,EAAA,EAAI,qBAAA;AAAA,IACJ,WAAA,EAAa,6EAAA;AAAA,IACb,KAAA,EAAO,iBAAA;AAAA,IACP,UAAA,EAAY;AAAA,MACV,6CAAA;AAAA,MACA,uDAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,KAAA,EAAO;AAAA,MACL,cAAA;AAAA,MACA,gBAAA;AAAA,MACA,sBAAA;AAAA,MACA,oBAAA;AAAA,MACA,qBAAA;AAAA,MACA,sBAAA;AAAA,MACA,qBAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,SAAA,EAAW,CAAC,EAAE,GAAA,EAAK,gBAAgB;AAAA,GACpC,CAAA;AAAA,EACD,IAAA,EACE;AAKJ;AC9BO,IAAM,cAAc,cAAA,CAAe;AAAA,EACxC,EAAA,EAAI,cAAA;AAAA,EACJ,IAAA,EAAM,cAAA;AAAA,EACN,WAAA,EAAa,uEAAA;AAAA,EACb,OAAA,EAAS,OAAA;AAAA,EACT,QAAQ,EAAC;AAAA,EACT,SAAS,EAAC;AAAA,EACV,KAAA,EAAO;AAAA,IACL;AAAA,MACE,EAAA,EAAI,QAAA;AAAA,MACJ,IAAA,EAAM,OAAA;AAAA,MACN,KAAA,EAAO,EAAE,GAAA,EAAK,qBAAA,EAAsB;AAAA,MACpC,MAAA,EACE;AAAA;AAIJ;AAEJ,CAAC;;;ACVM,IAAM,aAAwB,SAAA,CAAU;AAAA,EAC7C,MAAA,EAAQ,CAAC,OAAO,CAAA;AAAA,EAChB,SAAA,EAAW,CAAC,WAAW;AACzB,CAAC","file":"mail-triage.mjs","sourcesContent":["import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Triages your inbox — finds unread mail, categorizes, labels, archives. */\nexport const triager: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/triager\",\n description: \"Triages your inbox — finds unread mail, categorizes, labels, archives.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Always show the triage plan before applying\",\n \"Never delete — only archive, label, or mark read\",\n \"Ask before applying bulk actions on more than 20 messages\",\n ],\n tools: [\n \"mailbox_list\",\n \"mailbox_search\",\n \"mailbox_list_threads\",\n \"mailbox_get_thread\",\n \"mailbox_labels_list\",\n \"mailbox_label_create\",\n \"mailbox_triage_plan\",\n \"mailbox_triage_apply\",\n ],\n workflows: [{ ref: \"triage-inbox\" }],\n }),\n body:\n \"You scan the inbox for unread messages using mailbox_search. Categorize them \" +\n \"(urgent, needs-reply, newsletter, notification, spam). Create a triage plan: \" +\n \"label each category, archive newsletters+notifications, keep urgent+needs-reply \" +\n \"in inbox. Show the plan before applying. Use mailbox_triage_plan to build the \" +\n \"plan and mailbox_triage_apply to execute it after user confirmation.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/** Single-agent workflow: triage the inbox. */\nexport const triageInbox = defineWorkflow({\n id: \"triage-inbox\",\n name: \"Triage Inbox\",\n description: \"Scan inbox, categorize unread messages, create and apply triage plan.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"triage\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/triager\" },\n prompt:\n \"Scan the inbox for unread messages. Categorize them (urgent, needs-reply, \" +\n \"newsletter, notification, spam). Create a triage plan: label each category, \" +\n \"archive newsletters+notifications, keep urgent+needs-reply in inbox. \" +\n \"Show me the plan before applying.\",\n },\n ],\n})\n","/**\n * `mail-triage` — a single-agent agentproto app for email triage.\n *\n * One agent (triager) scans inbox, categorizes unread mail, and applies\n * triage actions (label, archive). Proves the APP primitive works end-to-end\n * with a simple, useful real-world task.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { triager } from \"./agents/triager.js\"\nimport { triageInbox } from \"./workflows/triage-inbox.js\"\n\nexport const mailTriage: AppHandle = defineApp({\n agents: [triager],\n workflows: [triageInbox],\n})\n\nexport { triager, triageInbox }\n"]}
1
+ {"version":3,"sources":["../src/mail-triage/agents/triager.ts","../src/mail-triage/workflows/triage-inbox.ts","../src/mail-triage/ui.ts","../src/mail-triage/index.ts"],"names":[],"mappings":"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","file":"mail-triage.mjs","sourcesContent":["import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Triages your inbox — finds unread mail, categorizes, labels, archives. */\nexport const triager: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/triager\",\n description: \"Triages your inbox — finds unread mail, categorizes, labels, archives.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Always show the triage plan before applying\",\n \"Never delete — only archive, label, or mark read\",\n \"Ask before applying bulk actions on more than 20 messages\",\n ],\n tools: [\n \"mailbox_list\",\n \"mailbox_search\",\n \"mailbox_list_threads\",\n \"mailbox_get_thread\",\n \"mailbox_labels_list\",\n \"mailbox_label_create\",\n \"mailbox_triage_plan\",\n \"mailbox_triage_apply\",\n ],\n workflows: [{ ref: \"triage-inbox\" }],\n }),\n body:\n \"You scan the inbox for unread messages using mailbox_search. Categorize them \" +\n \"(urgent, needs-reply, newsletter, notification, spam). Create a triage plan: \" +\n \"label each category, archive newsletters+notifications, keep urgent+needs-reply \" +\n \"in inbox. Show the plan before applying. Use mailbox_triage_plan to build the \" +\n \"plan and mailbox_triage_apply to execute it after user confirmation.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/** Single-agent workflow: triage the inbox. */\nexport const triageInbox = defineWorkflow({\n id: \"triage-inbox\",\n name: \"Triage Inbox\",\n description: \"Scan inbox, categorize unread messages, create and apply triage plan.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"triage\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/triager\" },\n prompt:\n \"Scan the inbox for unread messages. Categorize them (urgent, needs-reply, \" +\n \"newsletter, notification, spam). Create a triage plan: label each category, \" +\n \"archive newsletters+notifications, keep urgent+needs-reply in inbox. \" +\n \"Show me the plan before applying.\",\n },\n ],\n})\n","/**\n * Mail Triage — a self-contained MCP App dashboard for `mail-triage`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }`. Mailbox access goes\n * through the imported `agentpush` MCP server (`imported:agentpush/<tool>`\n * ids); running the triager agent itself goes through the daemon's own\n * `app_run` / `app_status` / `agent_output` — a second path alongside the\n * deterministic plan/apply tools, so the panel can either drive the mailbox\n * tools directly or hand the whole job to the agent and watch it work.\n */\n\nexport const MAIL_TRIAGE_TOOLS = [\n \"imported:agentpush/mailbox_list\",\n \"imported:agentpush/mailbox_search\",\n \"imported:agentpush/mailbox_triage_plan\",\n \"imported:agentpush/mailbox_triage_apply\",\n \"app_run\",\n \"app_status\",\n \"agent_output\",\n] as const\n\nconst APP_ID = \"@agentproto/mail-triage\"\nconst AGENT_ID = \"@agentproto/triager\"\nconst CATEGORIES = [\"urgent\", \"needs-reply\", \"newsletter\", \"notification\"]\n\nexport const MAIL_TRIAGE_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Mail Triage</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;\n --text:#e6edf3;--text2:#8b949e;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%;overflow-y:auto}\n#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0}\n#toolbar .title{font-size:13px;font-weight:600;flex:1}\nselect,input[type=text]{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 8px;border-radius:4px;font-size:12px;font-family:inherit}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n.abtn:disabled{opacity:.4;cursor:default}\n.abtn.primary{background:var(--blue);border-color:var(--blue);color:#04101c}\n.abtn.primary:hover{opacity:.9}\n#content{padding:14px;display:flex;flex-direction:column;gap:16px}\nsection{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:12px}\nsection h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px}\n#summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px}\n.tile{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:10px;text-align:center}\n.tile .count{font-size:22px;font-weight:700}\n.tile .label{font-size:11px;color:var(--text2);text-transform:capitalize}\n.tile.urgent .count{color:var(--red)}\n.tile.needs-reply .count{color:var(--yellow)}\n.tile.newsletter .count{color:var(--blue)}\n.tile.notification .count{color:var(--purple)}\n#plan-list,#search-results,#log{max-height:260px;overflow-y:auto;font-size:12px}\n.plan-item{display:flex;justify-content:space-between;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)}\n.plan-item .subj{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.plan-item .tag{color:var(--text2);font-size:11px;white-space:nowrap}\n#log{font-family:Menlo,Monaco,monospace;white-space:pre-wrap;word-break:break-word;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:8px}\n.searchbar{display:flex;gap:8px;margin-bottom:8px}\n.searchbar input{flex:1}\n.actions{display:flex;gap:8px;margin-top:10px}\n#status{padding:6px 14px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n.empty{color:var(--text2);padding:8px 0}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"toolbar\">\n <span class=\"title\">Mail Triage</span>\n <select id=\"mailbox-select\"></select>\n <button class=\"abtn\" id=\"refresh-btn\">Refresh</button>\n </div>\n <div id=\"content\">\n <section>\n <h3>Inbox summary</h3>\n <div id=\"summary\"></div>\n </section>\n\n <section>\n <h3>Search</h3>\n <div class=\"searchbar\">\n <input type=\"text\" id=\"search-input\" placeholder=\"e.g. is:unread\">\n <button class=\"abtn\" id=\"search-btn\">Search</button>\n </div>\n <div id=\"search-results\"><div class=\"empty\">No search run yet.</div></div>\n </section>\n\n <section>\n <h3>Triage plan</h3>\n <div class=\"actions\">\n <button class=\"abtn primary\" id=\"triage-btn\">Triage</button>\n <button class=\"abtn\" id=\"apply-btn\" disabled>Apply</button>\n </div>\n <div id=\"plan-list\" style=\"margin-top:10px\"><div class=\"empty\">No plan yet — click Triage.</div></div>\n </section>\n\n <section>\n <h3>Triager agent</h3>\n <div class=\"actions\">\n <button class=\"abtn\" id=\"run-agent-btn\">Run Triager Agent</button>\n </div>\n <div id=\"log\" style=\"margin-top:10px\"><div class=\"empty\">Agent has not run yet.</div></div>\n </section>\n </div>\n <div id=\"status\">Connecting&#8230;</div>\n</div>\n<script>\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar AGENT_ID = ${JSON.stringify(AGENT_ID)};\nvar CATEGORIES = ${JSON.stringify(CATEGORIES)};\nvar callTool = null;\nvar currentPlan = null;\nvar pollHandle = null;\n\nfunction setStatus(msg) {\n document.getElementById(\"status\").textContent = msg;\n}\n\n// See media-viewer/ui.ts for why this loop exists: app_tool_call wraps its\n// dispatch result in at least one more {content:[{type:\"text\",text:...}]}\n// shell than the underlying tool returned.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try {\n cur = JSON.parse(cur);\n continue;\n } catch (e) {\n return cur;\n }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction asList(value, keys) {\n if (Array.isArray(value)) return value;\n if (value && typeof value === \"object\") {\n for (var i = 0; i < keys.length; i++) {\n if (Array.isArray(value[keys[i]])) return value[keys[i]];\n }\n }\n return [];\n}\n\nfunction renderSummary(counts) {\n var el = document.getElementById(\"summary\");\n el.innerHTML = \"\";\n CATEGORIES.forEach(function (cat) {\n var tile = document.createElement(\"div\");\n tile.className = \"tile \" + cat;\n var count = counts && counts[cat] !== undefined ? counts[cat] : \"–\";\n tile.innerHTML = '<div class=\"count\"></div><div class=\"label\"></div>';\n tile.querySelector(\".count\").textContent = String(count);\n tile.querySelector(\".label\").textContent = cat.replace(\"-\", \" \");\n el.appendChild(tile);\n });\n}\n\nfunction loadMailboxes() {\n setStatus(\"Loading mailboxes\\\\u2026\");\n return callApp(\"imported:agentpush/mailbox_list\", {})\n .then(function (result) {\n var mailboxes = asList(result, [\"mailboxes\", \"items\"]);\n var select = document.getElementById(\"mailbox-select\");\n select.innerHTML = \"\";\n if (mailboxes.length === 0) {\n var opt = document.createElement(\"option\");\n opt.textContent = \"inbox\";\n opt.value = \"inbox\";\n select.appendChild(opt);\n } else {\n mailboxes.forEach(function (mb) {\n var name = typeof mb === \"string\" ? mb : mb.name || mb.id || JSON.stringify(mb);\n var opt = document.createElement(\"option\");\n opt.value = name;\n opt.textContent = name;\n select.appendChild(opt);\n });\n }\n setStatus(\"Ready\");\n })\n .catch(function (err) {\n setStatus(\"Error loading mailboxes: \" + err.message);\n });\n}\n\nfunction renderSearchResults(items) {\n var el = document.getElementById(\"search-results\");\n el.innerHTML = \"\";\n if (items.length === 0) {\n el.innerHTML = '<div class=\"empty\">No results.</div>';\n return;\n }\n items.forEach(function (item) {\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n var subj = typeof item === \"string\" ? item : item.subject || item.title || JSON.stringify(item);\n var from = item && item.from ? item.from : \"\";\n row.innerHTML = '<span class=\"subj\"></span><span class=\"tag\"></span>';\n row.querySelector(\".subj\").textContent = subj;\n row.querySelector(\".tag\").textContent = from;\n el.appendChild(row);\n });\n}\n\nfunction renderPlan(plan) {\n var items = asList(plan, [\"actions\", \"items\", \"plan\"]);\n var el = document.getElementById(\"plan-list\");\n el.innerHTML = \"\";\n if (items.length === 0) {\n el.innerHTML = '<div class=\"empty\">Plan is empty.</div>';\n return;\n }\n var counts = {};\n items.forEach(function (item) {\n var cat = (item && (item.category || item.tag)) || \"notification\";\n counts[cat] = (counts[cat] || 0) + 1;\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n var subj = (item && (item.subject || item.title)) || JSON.stringify(item);\n var action = (item && (item.action || item.category)) || \"\";\n row.innerHTML = '<span class=\"subj\"></span><span class=\"tag\"></span>';\n row.querySelector(\".subj\").textContent = subj;\n row.querySelector(\".tag\").textContent = action;\n el.appendChild(row);\n });\n renderSummary(counts);\n}\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", loadMailboxes);\n\ndocument.getElementById(\"search-btn\").addEventListener(\"click\", function () {\n var query = document.getElementById(\"search-input\").value;\n var mailbox = document.getElementById(\"mailbox-select\").value;\n setStatus(\"Searching\\\\u2026\");\n callApp(\"imported:agentpush/mailbox_search\", { mailbox: mailbox, query: query })\n .then(function (result) {\n renderSearchResults(asList(result, [\"messages\", \"items\", \"results\"]));\n setStatus(\"Ready\");\n })\n .catch(function (err) {\n setStatus(\"Search failed: \" + err.message);\n });\n});\n\ndocument.getElementById(\"triage-btn\").addEventListener(\"click\", function () {\n var mailbox = document.getElementById(\"mailbox-select\").value;\n setStatus(\"Building triage plan\\\\u2026\");\n document.getElementById(\"apply-btn\").disabled = true;\n callApp(\"imported:agentpush/mailbox_triage_plan\", { mailbox: mailbox })\n .then(function (plan) {\n currentPlan = plan;\n renderPlan(plan);\n document.getElementById(\"apply-btn\").disabled = false;\n setStatus(\"Plan ready \\\\u2014 review, then Apply.\");\n })\n .catch(function (err) {\n setStatus(\"Triage plan failed: \" + err.message);\n });\n});\n\ndocument.getElementById(\"apply-btn\").addEventListener(\"click\", function () {\n if (!currentPlan) return;\n setStatus(\"Applying triage plan\\\\u2026\");\n callApp(\"imported:agentpush/mailbox_triage_apply\", { plan: currentPlan })\n .then(function () {\n setStatus(\"Plan applied.\");\n document.getElementById(\"apply-btn\").disabled = true;\n })\n .catch(function (err) {\n setStatus(\"Apply failed: \" + err.message);\n });\n});\n\nfunction appendLog(text) {\n var el = document.getElementById(\"log\");\n if (el.querySelector(\".empty\")) el.innerHTML = \"\";\n el.textContent += (el.textContent ? \"\\\\n\" : \"\") + text;\n el.scrollTop = el.scrollHeight;\n}\n\nfunction pollAgentRun(appRunId) {\n if (pollHandle) clearTimeout(pollHandle);\n callApp(\"app_status\", { appRunId: appRunId })\n .then(function (status) {\n var sessions = (status && status.sessions) || [];\n var sessionId = sessions[0] && sessions[0].sessionId;\n var tail = sessionId\n ? callApp(\"agent_output\", { sessionId: sessionId, lastN: 200, clean: true })\n : Promise.resolve(\"\");\n return tail.then(function (output) {\n if (output) appendLog(typeof output === \"string\" ? output : JSON.stringify(output));\n var done = status && (status.status === \"ended\" || status.status === \"done\" || status.endedAt);\n if (!done) {\n pollHandle = setTimeout(function () { pollAgentRun(appRunId); }, 2000);\n } else {\n setStatus(\"Agent run finished.\");\n }\n });\n })\n .catch(function (err) {\n setStatus(\"Agent status failed: \" + err.message);\n });\n}\n\ndocument.getElementById(\"run-agent-btn\").addEventListener(\"click\", function () {\n document.getElementById(\"log\").innerHTML = \"\";\n setStatus(\"Starting triager agent\\\\u2026\");\n callApp(\"app_run\", { appId: APP_ID, agents: [AGENT_ID], prompt: \"Triage my inbox.\" })\n .then(function (run) {\n appendLog(\"Started app run \" + (run && run.appRunId));\n pollAgentRun(run.appRunId);\n })\n .catch(function (err) {\n setStatus(\"Run failed: \" + err.message);\n });\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n return loadMailboxes();\n })\n .catch(function (err) {\n setStatus(\"Standalone mode \\\\u2014 no host bridge (\" + err.message + \")\");\n renderSummary(null);\n });\n\nrenderSummary(null);\n</script>\n</body>\n</html>`\n","/**\n * `mail-triage` — a single-agent agentproto app for email triage.\n *\n * One agent (triager) scans inbox, categorizes unread mail, and applies\n * triage actions (label, archive). Proves the APP primitive works end-to-end\n * with a simple, useful real-world task.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { triager } from \"./agents/triager.js\"\nimport { triageInbox } from \"./workflows/triage-inbox.js\"\nimport { MAIL_TRIAGE_HTML, MAIL_TRIAGE_TOOLS } from \"./ui.js\"\n\nexport const mailTriage: AppHandle = defineApp({\n id: \"@agentproto/mail-triage\",\n name: \"Mail Triage\",\n version: \"0.1.0\",\n description: \"Triage your inbox — categorize, label, archive.\",\n agents: [triager],\n workflows: [triageInbox],\n ui: {\n html: MAIL_TRIAGE_HTML,\n title: \"Mail Triage\",\n tools: [...MAIL_TRIAGE_TOOLS],\n },\n})\n\nexport { triager, triageInbox }\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media-viewer/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAErD,eAAO,MAAM,WAAW,EAAE,SAGxB,CAAA;AAEF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media-viewer/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAGrD,eAAO,MAAM,WAAW,EAAE,SAYxB,CAAA;AAEF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Media Viewer — a self-contained MCP App gallery widget for `media-viewer`.
3
+ *
4
+ * Talks to the daemon only through the `window.McpApp.connect()` bridge
5
+ * (never a direct `fetch`): every action round-trips through the app's own
6
+ * `app_tool_call` gateway tool, `{ appId, tool, args }`. `app_tool_call`
7
+ * double-wraps its dispatch result (it returns a text-result whose `text` is
8
+ * the JSON-stringified inner tool result, itself an MCP content array) — see
9
+ * `unwrapText` below, which peels that apart regardless of how many layers
10
+ * the host's bridge already stripped.
11
+ */
12
+ export declare const MEDIA_VIEWER_TOOLS: readonly ["directory_list", "file_info", "file_read"];
13
+ export declare const MEDIA_VIEWER_HTML: string;
14
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/media-viewer/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,kBAAkB,uDAAwD,CAAA;AAIvF,eAAO,MAAM,iBAAiB,QA4OtB,CAAA"}
@@ -71,10 +71,260 @@ var scanMedia = defineWorkflow({
71
71
  ]
72
72
  });
73
73
 
74
+ // src/media-viewer/ui.ts
75
+ var MEDIA_VIEWER_TOOLS = ["directory_list", "file_info", "file_read"];
76
+ var APP_ID = "@agentproto/media-viewer";
77
+ var MEDIA_VIEWER_HTML = `<!DOCTYPE html>
78
+ <html lang="en">
79
+ <head>
80
+ <meta charset="UTF-8">
81
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
82
+ <title>Media Viewer</title>
83
+ <style>
84
+ *{box-sizing:border-box;margin:0;padding:0}
85
+ :root{
86
+ --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
87
+ --text:#e6edf3;--text2:#8b949e;
88
+ --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
89
+ }
90
+ html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}
91
+ #app{display:flex;flex-direction:column;height:100%}
92
+ #toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;background:var(--bg2);flex-shrink:0}
93
+ #toolbar .title{font-size:13px;font-weight:600;flex:1}
94
+ #breadcrumb{font-size:12px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
95
+ .abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}
96
+ .abtn:hover{background:var(--border)}
97
+ #body{flex:1;display:flex;overflow:hidden}
98
+ #grid-wrap{flex:1;overflow-y:auto;padding:14px}
99
+ #grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px}
100
+ .cell{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);cursor:pointer;text-align:center}
101
+ .cell:hover{background:var(--bg3);border-color:var(--blue)}
102
+ .cell.selected{border-color:var(--blue);background:var(--bg3)}
103
+ .icon{font-size:28px;line-height:1}
104
+ .name{font-size:11px;color:var(--text);word-break:break-word;max-width:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
105
+ #empty{color:var(--text2);padding:24px;text-align:center}
106
+ #details{width:280px;border-left:1px solid var(--border);background:var(--bg2);padding:14px;overflow-y:auto;flex-shrink:0}
107
+ #details h3{font-size:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}
108
+ #details .row{display:flex;justify-content:space-between;gap:8px;padding:4px 0;border-bottom:1px solid var(--border);font-size:12px}
109
+ #details .row span:first-child{color:var(--text2)}
110
+ #preview{margin-top:10px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-family:Menlo,Monaco,monospace;font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:240px;overflow-y:auto}
111
+ #status{padding:6px 14px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <div id="app">
116
+ <div id="toolbar">
117
+ <button class="abtn" id="up-btn" title="Up one level">&uarr;</button>
118
+ <span class="title">Media Viewer</span>
119
+ <span id="breadcrumb">.</span>
120
+ <button class="abtn" id="refresh-btn">Refresh</button>
121
+ </div>
122
+ <div id="body">
123
+ <div id="grid-wrap">
124
+ <div id="grid"></div>
125
+ <div id="empty" style="display:none">No files in this folder.</div>
126
+ </div>
127
+ <div id="details" style="display:none"></div>
128
+ </div>
129
+ <div id="status">Connecting&#8230;</div>
130
+ </div>
131
+ <script>
132
+ var APP_ID = ${JSON.stringify(APP_ID)};
133
+ var currentPath = ".";
134
+ var entries = [];
135
+ var callTool = null;
136
+
137
+ function setStatus(msg) {
138
+ document.getElementById("status").textContent = msg;
139
+ }
140
+
141
+ // app_tool_call wraps its dispatch result at least once (its own
142
+ // text-result body is the JSON-stringified inner MCP tool result); peel
143
+ // back through nested {content:[{type:"text",text:...}]} shells until the
144
+ // payload stops looking like one, so this works whichever layer the host's
145
+ // own bridge already unwrapped.
146
+ function unwrapText(result) {
147
+ var cur = result;
148
+ for (var i = 0; i < 4; i++) {
149
+ if (typeof cur === "string") {
150
+ try {
151
+ cur = JSON.parse(cur);
152
+ continue;
153
+ } catch (e) {
154
+ return cur;
155
+ }
156
+ }
157
+ if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === "string") {
158
+ cur = cur.content[0].text;
159
+ continue;
160
+ }
161
+ break;
162
+ }
163
+ return cur;
164
+ }
165
+
166
+ function callApp(tool, args) {
167
+ return callTool("app_tool_call", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);
168
+ }
169
+
170
+ function parseDirectoryListing(raw) {
171
+ var text = typeof raw === "string" ? raw : "";
172
+ return text
173
+ .split("\\n")
174
+ .map(function (line) { return line.trim(); })
175
+ .filter(function (line) { return line.length > 0; })
176
+ .map(function (line) {
177
+ var isDir = line.indexOf("[DIR]") === 0;
178
+ var name = line.replace(/^\\[(FILE|DIR)\\]\\s*/, "");
179
+ return { name: name, isDir: isDir };
180
+ });
181
+ }
182
+
183
+ function iconFor(entry) {
184
+ if (entry.isDir) return "\\uD83D\\uDCC1";
185
+ var ext = (entry.name.split(".").pop() || "").toLowerCase();
186
+ if (["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "ico", "avif"].indexOf(ext) !== -1) return "\\uD83D\\uDDBC\\uFE0F";
187
+ if (["mp4", "webm", "mov", "avi", "mkv"].indexOf(ext) !== -1) return "\\uD83C\\uDFA5";
188
+ if (["mp3", "wav", "ogg", "flac", "aac", "m4a"].indexOf(ext) !== -1) return "\\uD83C\\uDFB5";
189
+ return "\\uD83D\\uDCC4";
190
+ }
191
+
192
+ function joinPath(base, name) {
193
+ if (base === "." || base === "") return name;
194
+ return base.replace(/\\/+$/, "") + "/" + name;
195
+ }
196
+
197
+ function parentPath(path) {
198
+ if (path === "." || path.indexOf("/") === -1) return ".";
199
+ return path.slice(0, path.lastIndexOf("/")) || ".";
200
+ }
201
+
202
+ function renderGrid() {
203
+ document.getElementById("breadcrumb").textContent = currentPath;
204
+ var grid = document.getElementById("grid");
205
+ var empty = document.getElementById("empty");
206
+ grid.innerHTML = "";
207
+ if (entries.length === 0) {
208
+ empty.style.display = "block";
209
+ return;
210
+ }
211
+ empty.style.display = "none";
212
+ entries.forEach(function (entry, idx) {
213
+ var cell = document.createElement("div");
214
+ cell.className = "cell";
215
+ cell.dataset.idx = String(idx);
216
+ cell.innerHTML = '<div class="icon">' + iconFor(entry) + '</div><div class="name"></div>';
217
+ cell.querySelector(".name").textContent = entry.name;
218
+ cell.addEventListener("click", function () { onSelect(entry, cell); });
219
+ grid.appendChild(cell);
220
+ });
221
+ }
222
+
223
+ function onSelect(entry, cell) {
224
+ Array.prototype.forEach.call(document.querySelectorAll(".cell"), function (c) {
225
+ c.classList.remove("selected");
226
+ });
227
+ cell.classList.add("selected");
228
+ if (entry.isDir) {
229
+ loadDirectory(joinPath(currentPath, entry.name));
230
+ return;
231
+ }
232
+ showFileDetails(joinPath(currentPath, entry.name));
233
+ }
234
+
235
+ function showFileDetails(path) {
236
+ var details = document.getElementById("details");
237
+ details.style.display = "block";
238
+ details.innerHTML = "<h3>Loading&#8230;</h3>";
239
+ setStatus("Reading " + path + "\\u2026");
240
+ callApp("file_info", { path: path })
241
+ .then(function (info) {
242
+ var rows = "";
243
+ if (info && typeof info === "object") {
244
+ ["name", "type", "size", "modified"].forEach(function (key) {
245
+ if (info[key] !== undefined) {
246
+ rows += '<div class="row"><span>' + key + '</span><span></span></div>';
247
+ }
248
+ });
249
+ }
250
+ details.innerHTML = "<h3>Details</h3>" + rows + '<div id="preview">Loading preview&#8230;</div>';
251
+ var rowEls = details.querySelectorAll(".row span:last-child");
252
+ var keys = ["name", "type", "size", "modified"].filter(function (key) {
253
+ return info && info[key] !== undefined;
254
+ });
255
+ keys.forEach(function (key, i) {
256
+ rowEls[i].textContent = String(info[key]);
257
+ });
258
+ return callApp("file_read", { path: path });
259
+ })
260
+ .then(function (content) {
261
+ var pre = document.getElementById("preview");
262
+ if (!pre) return;
263
+ var text = typeof content === "string" ? content : JSON.stringify(content);
264
+ pre.textContent = text.length > 2000 ? text.slice(0, 2000) + "\\u2026 (truncated)" : (text || "(empty file)");
265
+ setStatus("Ready \\u00b7 " + currentPath);
266
+ })
267
+ .catch(function (err) {
268
+ var pre = document.getElementById("preview");
269
+ if (pre) pre.textContent = "Preview unavailable (" + err.message + ")";
270
+ setStatus("Ready \\u00b7 " + currentPath);
271
+ });
272
+ }
273
+
274
+ function loadDirectory(path) {
275
+ document.getElementById("details").style.display = "none";
276
+ setStatus("Loading " + path + "\\u2026");
277
+ return callApp("directory_list", { path: path })
278
+ .then(function (raw) {
279
+ currentPath = path;
280
+ entries = parseDirectoryListing(raw).sort(function (a, b) {
281
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
282
+ return a.name.localeCompare(b.name);
283
+ });
284
+ renderGrid();
285
+ setStatus("Ready \\u00b7 " + entries.length + " entries \\u00b7 " + path);
286
+ })
287
+ .catch(function (err) {
288
+ entries = [];
289
+ renderGrid();
290
+ setStatus("Error: " + err.message);
291
+ });
292
+ }
293
+
294
+ document.getElementById("up-btn").addEventListener("click", function () {
295
+ loadDirectory(parentPath(currentPath));
296
+ });
297
+ document.getElementById("refresh-btn").addEventListener("click", function () {
298
+ loadDirectory(currentPath);
299
+ });
300
+
301
+ window.McpApp.connect()
302
+ .then(function (bridge) {
303
+ callTool = bridge.callTool;
304
+ return loadDirectory(".");
305
+ })
306
+ .catch(function (err) {
307
+ setStatus("Standalone mode \\u2014 no host bridge (" + err.message + ")");
308
+ entries = [];
309
+ renderGrid();
310
+ });
311
+ </script>
312
+ </body>
313
+ </html>`;
314
+
74
315
  // src/media-viewer/index.ts
75
316
  var mediaViewer = defineApp({
317
+ id: "@agentproto/media-viewer",
318
+ name: "Media Viewer",
319
+ version: "0.1.0",
320
+ description: "Scan a folder for images, video, and audio \u2014 gallery view.",
76
321
  agents: [cataloger],
77
- workflows: [scanMedia]
322
+ workflows: [scanMedia],
323
+ ui: {
324
+ html: MEDIA_VIEWER_HTML,
325
+ title: "Media Viewer",
326
+ tools: [...MEDIA_VIEWER_TOOLS]
327
+ }
78
328
  });
79
329
 
80
330
  export { cataloger, mediaViewer, scanMedia };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/media-viewer/agents/cataloger.ts","../src/media-viewer/workflows/scan-media.ts","../src/media-viewer/index.ts"],"names":[],"mappings":";;;;;;;;;AAGO,IAAM,SAAA,GAAwB;AAAA,EACnC,OAAO,WAAA,CAAY;AAAA,IACjB,MAAA,EAAQ,UAAA;AAAA,IACR,EAAA,EAAI,6BAAA;AAAA,IACJ,WAAA,EACE,8GAAA;AAAA,IACF,KAAA,EAAO,iBAAA;AAAA,IACP,UAAA,EAAY;AAAA,MACV,8DAAA;AAAA,MACA,qDAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,KAAA,EAAO,CAAC,UAAA,EAAY,WAAA,EAAa,WAAW,CAAA;AAAA,IAC5C,SAAA,EAAW,CAAC,EAAE,GAAA,EAAK,cAAc;AAAA,GAClC,CAAA;AAAA,EACD,IAAA,EAAM;AAAA,IACJ,oFAAA;AAAA,IACA,EAAA;AAAA,IACA,gFAAA;AAAA,IACA,sCAAA;AAAA,IACA,mEAAA;AAAA,IACA,wCAAA;AAAA,IACA,8CAAA;AAAA,IACA,EAAA;AAAA,IACA,6EAAA;AAAA,IACA,EAAA;AAAA,IACA,uCAAA;AAAA,IACA,SAAA;AAAA,IACA,GAAA;AAAA,IACA,+BAAA;AAAA,IACA,mCAAA;AAAA,IACA,wFAAA;AAAA,IACA,cAAA;AAAA,IACA,OAAA;AAAA,IACA,oCAAA;AAAA,IACA,2BAAA;AAAA,IACA,wBAAA;AAAA,IACA,4BAAA;AAAA,IACA,4BAAA;AAAA,IACA,4CAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,GAAA;AAAA,IACA,KAAA;AAAA,IACA,EAAA;AAAA,IACA;AAAA,GACF,CAAE,KAAK,IAAI;AACb;AChDO,IAAM,YAAY,cAAA,CAAe;AAAA,EACtC,EAAA,EAAI,YAAA;AAAA,EACJ,IAAA,EAAM,YAAA;AAAA,EACN,WAAA,EACE,oFAAA;AAAA,EACF,OAAA,EAAS,OAAA;AAAA,EACT,QAAQ,EAAC;AAAA,EACT,SAAS,EAAC;AAAA,EACV,KAAA,EAAO;AAAA,IACL;AAAA,MACE,EAAA,EAAI,SAAA;AAAA,MACJ,IAAA,EAAM,OAAA;AAAA,MACN,KAAA,EAAO,EAAE,GAAA,EAAK,6BAAA,EAA8B;AAAA,MAC5C,MAAA,EACE;AAAA;AAIJ;AAEJ,CAAC;;;ACVM,IAAM,cAAyB,SAAA,CAAU;AAAA,EAC9C,MAAA,EAAQ,CAAC,SAAS,CAAA;AAAA,EAClB,SAAA,EAAW,CAAC,SAAS;AACvB,CAAC","file":"media-viewer.mjs","sourcesContent":["import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\nexport const cataloger: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/media-cataloger\",\n description:\n \"Scans a folder for media files (images, video, audio), collects metadata, and produces a structured catalog.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Read-only — never modify, rename, move, or delete files\",\n \"Only catalog files with recognized media extensions\",\n \"Report exact file sizes and paths, never estimate\",\n ],\n tools: [\"list_dir\", \"read_file\", \"file_info\"],\n workflows: [{ ref: \"scan-media\" }],\n }),\n body: [\n \"You scan a workspace folder for media files and produce a structured JSON catalog.\",\n \"\",\n \"Walk the target directory recursively using list_dir. For each file, check the\",\n \"extension against known media types:\",\n \"- Images: .jpg, .jpeg, .png, .gif, .webp, .svg, .bmp, .ico, .avif\",\n \"- Video: .mp4, .webm, .mov, .avi, .mkv\",\n \"- Audio: .mp3, .wav, .ogg, .flac, .aac, .m4a\",\n \"\",\n \"For each media file found, use file_info to get size and modification date.\",\n \"\",\n \"Output a JSON object with this shape:\",\n \"```json\",\n '{',\n ' \"folder\": \"<scanned path>\",',\n ' \"scannedAt\": \"<ISO timestamp>\",',\n ' \"summary\": { \"total\": N, \"images\": N, \"video\": N, \"audio\": N, \"totalSizeBytes\": N },',\n ' \"files\": [',\n ' {',\n ' \"path\": \"relative/path.jpg\",',\n ' \"name\": \"file.jpg\",',\n ' \"type\": \"image\",',\n ' \"extension\": \".jpg\",',\n ' \"sizeBytes\": 123456,',\n ' \"modifiedAt\": \"2026-08-01T12:00:00Z\"',\n \" }\",\n \" ]\",\n \"}\",\n \"```\",\n \"\",\n \"Sort files by type (images first, then video, then audio), then by name.\",\n ].join(\"\\n\"),\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\nexport const scanMedia = defineWorkflow({\n id: \"scan-media\",\n name: \"Scan Media\",\n description:\n \"Scan a folder for media files, collect metadata, and produce a structured catalog.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"catalog\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/media-cataloger\" },\n prompt:\n \"Scan the workspace root folder for media files (images, video, audio). \" +\n \"Walk all subdirectories recursively. For each media file, collect its path, \" +\n \"name, type, extension, size, and modification date. Output the full catalog \" +\n \"as a single JSON object.\",\n },\n ],\n})\n","/**\n * `media-viewer` — a single-agent agentproto app for media cataloging.\n *\n * One agent (cataloger) scans a workspace folder for images, video, and audio\n * files, collects metadata, and produces a structured JSON catalog. Paired\n * with a Cowork McpApp gallery widget for visual browsing.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { cataloger } from \"./agents/cataloger.js\"\nimport { scanMedia } from \"./workflows/scan-media.js\"\n\nexport const mediaViewer: AppHandle = defineApp({\n agents: [cataloger],\n workflows: [scanMedia],\n})\n\nexport { cataloger, scanMedia }\n"]}
1
+ {"version":3,"sources":["../src/media-viewer/agents/cataloger.ts","../src/media-viewer/workflows/scan-media.ts","../src/media-viewer/ui.ts","../src/media-viewer/index.ts"],"names":[],"mappings":"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","file":"media-viewer.mjs","sourcesContent":["import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\nexport const cataloger: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/media-cataloger\",\n description:\n \"Scans a folder for media files (images, video, audio), collects metadata, and produces a structured catalog.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Read-only — never modify, rename, move, or delete files\",\n \"Only catalog files with recognized media extensions\",\n \"Report exact file sizes and paths, never estimate\",\n ],\n tools: [\"list_dir\", \"read_file\", \"file_info\"],\n workflows: [{ ref: \"scan-media\" }],\n }),\n body: [\n \"You scan a workspace folder for media files and produce a structured JSON catalog.\",\n \"\",\n \"Walk the target directory recursively using list_dir. For each file, check the\",\n \"extension against known media types:\",\n \"- Images: .jpg, .jpeg, .png, .gif, .webp, .svg, .bmp, .ico, .avif\",\n \"- Video: .mp4, .webm, .mov, .avi, .mkv\",\n \"- Audio: .mp3, .wav, .ogg, .flac, .aac, .m4a\",\n \"\",\n \"For each media file found, use file_info to get size and modification date.\",\n \"\",\n \"Output a JSON object with this shape:\",\n \"```json\",\n '{',\n ' \"folder\": \"<scanned path>\",',\n ' \"scannedAt\": \"<ISO timestamp>\",',\n ' \"summary\": { \"total\": N, \"images\": N, \"video\": N, \"audio\": N, \"totalSizeBytes\": N },',\n ' \"files\": [',\n ' {',\n ' \"path\": \"relative/path.jpg\",',\n ' \"name\": \"file.jpg\",',\n ' \"type\": \"image\",',\n ' \"extension\": \".jpg\",',\n ' \"sizeBytes\": 123456,',\n ' \"modifiedAt\": \"2026-08-01T12:00:00Z\"',\n \" }\",\n \" ]\",\n \"}\",\n \"```\",\n \"\",\n \"Sort files by type (images first, then video, then audio), then by name.\",\n ].join(\"\\n\"),\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\nexport const scanMedia = defineWorkflow({\n id: \"scan-media\",\n name: \"Scan Media\",\n description:\n \"Scan a folder for media files, collect metadata, and produce a structured catalog.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"catalog\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/media-cataloger\" },\n prompt:\n \"Scan the workspace root folder for media files (images, video, audio). \" +\n \"Walk all subdirectories recursively. For each media file, collect its path, \" +\n \"name, type, extension, size, and modification date. Output the full catalog \" +\n \"as a single JSON object.\",\n },\n ],\n})\n","/**\n * Media Viewer — a self-contained MCP App gallery widget for `media-viewer`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }`. `app_tool_call`\n * double-wraps its dispatch result (it returns a text-result whose `text` is\n * the JSON-stringified inner tool result, itself an MCP content array) — see\n * `unwrapText` below, which peels that apart regardless of how many layers\n * the host's bridge already stripped.\n */\n\nexport const MEDIA_VIEWER_TOOLS = [\"directory_list\", \"file_info\", \"file_read\"] as const\n\nconst APP_ID = \"@agentproto/media-viewer\"\n\nexport const MEDIA_VIEWER_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Media Viewer</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;\n --text:#e6edf3;--text2:#8b949e;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%}\n#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;background:var(--bg2);flex-shrink:0}\n#toolbar .title{font-size:13px;font-weight:600;flex:1}\n#breadcrumb{font-size:12px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n#body{flex:1;display:flex;overflow:hidden}\n#grid-wrap{flex:1;overflow-y:auto;padding:14px}\n#grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px}\n.cell{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);cursor:pointer;text-align:center}\n.cell:hover{background:var(--bg3);border-color:var(--blue)}\n.cell.selected{border-color:var(--blue);background:var(--bg3)}\n.icon{font-size:28px;line-height:1}\n.name{font-size:11px;color:var(--text);word-break:break-word;max-width:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}\n#empty{color:var(--text2);padding:24px;text-align:center}\n#details{width:280px;border-left:1px solid var(--border);background:var(--bg2);padding:14px;overflow-y:auto;flex-shrink:0}\n#details h3{font-size:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}\n#details .row{display:flex;justify-content:space-between;gap:8px;padding:4px 0;border-bottom:1px solid var(--border);font-size:12px}\n#details .row span:first-child{color:var(--text2)}\n#preview{margin-top:10px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-family:Menlo,Monaco,monospace;font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:240px;overflow-y:auto}\n#status{padding:6px 14px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"toolbar\">\n <button class=\"abtn\" id=\"up-btn\" title=\"Up one level\">&uarr;</button>\n <span class=\"title\">Media Viewer</span>\n <span id=\"breadcrumb\">.</span>\n <button class=\"abtn\" id=\"refresh-btn\">Refresh</button>\n </div>\n <div id=\"body\">\n <div id=\"grid-wrap\">\n <div id=\"grid\"></div>\n <div id=\"empty\" style=\"display:none\">No files in this folder.</div>\n </div>\n <div id=\"details\" style=\"display:none\"></div>\n </div>\n <div id=\"status\">Connecting&#8230;</div>\n</div>\n<script>\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar currentPath = \".\";\nvar entries = [];\nvar callTool = null;\n\nfunction setStatus(msg) {\n document.getElementById(\"status\").textContent = msg;\n}\n\n// app_tool_call wraps its dispatch result at least once (its own\n// text-result body is the JSON-stringified inner MCP tool result); peel\n// back through nested {content:[{type:\"text\",text:...}]} shells until the\n// payload stops looking like one, so this works whichever layer the host's\n// own bridge already unwrapped.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try {\n cur = JSON.parse(cur);\n continue;\n } catch (e) {\n return cur;\n }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction parseDirectoryListing(raw) {\n var text = typeof raw === \"string\" ? raw : \"\";\n return text\n .split(\"\\\\n\")\n .map(function (line) { return line.trim(); })\n .filter(function (line) { return line.length > 0; })\n .map(function (line) {\n var isDir = line.indexOf(\"[DIR]\") === 0;\n var name = line.replace(/^\\\\[(FILE|DIR)\\\\]\\\\s*/, \"\");\n return { name: name, isDir: isDir };\n });\n}\n\nfunction iconFor(entry) {\n if (entry.isDir) return \"\\\\uD83D\\\\uDCC1\";\n var ext = (entry.name.split(\".\").pop() || \"\").toLowerCase();\n if ([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"bmp\", \"ico\", \"avif\"].indexOf(ext) !== -1) return \"\\\\uD83D\\\\uDDBC\\\\uFE0F\";\n if ([\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"].indexOf(ext) !== -1) return \"\\\\uD83C\\\\uDFA5\";\n if ([\"mp3\", \"wav\", \"ogg\", \"flac\", \"aac\", \"m4a\"].indexOf(ext) !== -1) return \"\\\\uD83C\\\\uDFB5\";\n return \"\\\\uD83D\\\\uDCC4\";\n}\n\nfunction joinPath(base, name) {\n if (base === \".\" || base === \"\") return name;\n return base.replace(/\\\\/+$/, \"\") + \"/\" + name;\n}\n\nfunction parentPath(path) {\n if (path === \".\" || path.indexOf(\"/\") === -1) return \".\";\n return path.slice(0, path.lastIndexOf(\"/\")) || \".\";\n}\n\nfunction renderGrid() {\n document.getElementById(\"breadcrumb\").textContent = currentPath;\n var grid = document.getElementById(\"grid\");\n var empty = document.getElementById(\"empty\");\n grid.innerHTML = \"\";\n if (entries.length === 0) {\n empty.style.display = \"block\";\n return;\n }\n empty.style.display = \"none\";\n entries.forEach(function (entry, idx) {\n var cell = document.createElement(\"div\");\n cell.className = \"cell\";\n cell.dataset.idx = String(idx);\n cell.innerHTML = '<div class=\"icon\">' + iconFor(entry) + '</div><div class=\"name\"></div>';\n cell.querySelector(\".name\").textContent = entry.name;\n cell.addEventListener(\"click\", function () { onSelect(entry, cell); });\n grid.appendChild(cell);\n });\n}\n\nfunction onSelect(entry, cell) {\n Array.prototype.forEach.call(document.querySelectorAll(\".cell\"), function (c) {\n c.classList.remove(\"selected\");\n });\n cell.classList.add(\"selected\");\n if (entry.isDir) {\n loadDirectory(joinPath(currentPath, entry.name));\n return;\n }\n showFileDetails(joinPath(currentPath, entry.name));\n}\n\nfunction showFileDetails(path) {\n var details = document.getElementById(\"details\");\n details.style.display = \"block\";\n details.innerHTML = \"<h3>Loading&#8230;</h3>\";\n setStatus(\"Reading \" + path + \"\\\\u2026\");\n callApp(\"file_info\", { path: path })\n .then(function (info) {\n var rows = \"\";\n if (info && typeof info === \"object\") {\n [\"name\", \"type\", \"size\", \"modified\"].forEach(function (key) {\n if (info[key] !== undefined) {\n rows += '<div class=\"row\"><span>' + key + '</span><span></span></div>';\n }\n });\n }\n details.innerHTML = \"<h3>Details</h3>\" + rows + '<div id=\"preview\">Loading preview&#8230;</div>';\n var rowEls = details.querySelectorAll(\".row span:last-child\");\n var keys = [\"name\", \"type\", \"size\", \"modified\"].filter(function (key) {\n return info && info[key] !== undefined;\n });\n keys.forEach(function (key, i) {\n rowEls[i].textContent = String(info[key]);\n });\n return callApp(\"file_read\", { path: path });\n })\n .then(function (content) {\n var pre = document.getElementById(\"preview\");\n if (!pre) return;\n var text = typeof content === \"string\" ? content : JSON.stringify(content);\n pre.textContent = text.length > 2000 ? text.slice(0, 2000) + \"\\\\u2026 (truncated)\" : (text || \"(empty file)\");\n setStatus(\"Ready \\\\u00b7 \" + currentPath);\n })\n .catch(function (err) {\n var pre = document.getElementById(\"preview\");\n if (pre) pre.textContent = \"Preview unavailable (\" + err.message + \")\";\n setStatus(\"Ready \\\\u00b7 \" + currentPath);\n });\n}\n\nfunction loadDirectory(path) {\n document.getElementById(\"details\").style.display = \"none\";\n setStatus(\"Loading \" + path + \"\\\\u2026\");\n return callApp(\"directory_list\", { path: path })\n .then(function (raw) {\n currentPath = path;\n entries = parseDirectoryListing(raw).sort(function (a, b) {\n if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n renderGrid();\n setStatus(\"Ready \\\\u00b7 \" + entries.length + \" entries \\\\u00b7 \" + path);\n })\n .catch(function (err) {\n entries = [];\n renderGrid();\n setStatus(\"Error: \" + err.message);\n });\n}\n\ndocument.getElementById(\"up-btn\").addEventListener(\"click\", function () {\n loadDirectory(parentPath(currentPath));\n});\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n loadDirectory(currentPath);\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n return loadDirectory(\".\");\n })\n .catch(function (err) {\n setStatus(\"Standalone mode \\\\u2014 no host bridge (\" + err.message + \")\");\n entries = [];\n renderGrid();\n });\n</script>\n</body>\n</html>`\n","/**\n * `media-viewer` — a single-agent agentproto app for media cataloging.\n *\n * One agent (cataloger) scans a workspace folder for images, video, and audio\n * files, collects metadata, and produces a structured JSON catalog. Paired\n * with a Cowork McpApp gallery widget for visual browsing.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { cataloger } from \"./agents/cataloger.js\"\nimport { scanMedia } from \"./workflows/scan-media.js\"\nimport { MEDIA_VIEWER_HTML, MEDIA_VIEWER_TOOLS } from \"./ui.js\"\n\nexport const mediaViewer: AppHandle = defineApp({\n id: \"@agentproto/media-viewer\",\n name: \"Media Viewer\",\n version: \"0.1.0\",\n description: \"Scan a folder for images, video, and audio — gallery view.\",\n agents: [cataloger],\n workflows: [scanMedia],\n ui: {\n html: MEDIA_VIEWER_HTML,\n title: \"Media Viewer\",\n tools: [...MEDIA_VIEWER_TOOLS],\n },\n})\n\nexport { cataloger, scanMedia }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/apps",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "@agentproto/apps — a home for ready-made agentproto apps (teams of agents + their workflows), declared with @agentproto/app-kit. Import a team (code-team, content-team, …) and use any of its agents/workflows from your own host: in-process via toMastraAgents(), or emit its manifests to disk.",
5
5
  "keywords": [
6
6
  "agentproto",
@@ -25,6 +25,9 @@
25
25
  "main": "dist/index.mjs",
26
26
  "module": "dist/index.mjs",
27
27
  "types": "dist/index.d.ts",
28
+ "bin": {
29
+ "agentproto-apps-sync": "dist/bin/sync.mjs"
30
+ },
28
31
  "exports": {
29
32
  ".": {
30
33
  "types": "./dist/index.d.ts",
@@ -63,7 +66,7 @@
63
66
  },
64
67
  "dependencies": {
65
68
  "@agentproto/agent": "0.2.1",
66
- "@agentproto/app-kit": "0.4.0",
69
+ "@agentproto/app-kit": "0.5.0",
67
70
  "@agentproto/workflow": "0.2.0"
68
71
  },
69
72
  "peerDependencies": {