shadow-claw 1.28.0 → 1.29.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.
Files changed (194) hide show
  1. package/.well-known/mcp/server-card.json +1 -1
  2. package/.well-known/mcp.json +1 -1
  3. package/README.md +96 -278
  4. package/bin/cli.mjs +4 -0
  5. package/bin/commands/mcp.mjs +409 -125
  6. package/bin/commands/mcp.test.mjs +151 -18
  7. package/bin/utils/control-client.mjs +338 -103
  8. package/bin/utils/control-client.test.mjs +14 -0
  9. package/dist/electron/main.cjs +179 -179
  10. package/dist/lib/components/index.js +21 -21
  11. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  12. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  13. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  14. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  15. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  16. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  17. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  18. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  19. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  20. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  21. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  22. package/dist/lib/components/shadow-claw-channels.js +1 -1
  23. package/dist/lib/components/shadow-claw-chat.js +1 -1
  24. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  25. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  26. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  27. package/dist/lib/components/shadow-claw-files.js +1 -1
  28. package/dist/lib/components/shadow-claw-git.js +1 -1
  29. package/dist/lib/components/shadow-claw-llm.js +1 -1
  30. package/dist/lib/components/shadow-claw-networking.js +1 -1
  31. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  32. package/dist/lib/components/shadow-claw-pages.js +1 -1
  33. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  34. package/dist/lib/components/shadow-claw-settings.js +1 -1
  35. package/dist/lib/components/shadow-claw-storage.js +1 -1
  36. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  37. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  38. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  39. package/dist/lib/components/shadow-claw-tools.js +1 -1
  40. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  41. package/dist/lib/components/shadow-claw.js +1 -1
  42. package/dist/lib/{configurePeerJs-D8Ph0-A9.js → configurePeerJs-DG4hliKp.js} +2 -2
  43. package/dist/lib/{configurePeerJs-D8Ph0-A9.js.map → configurePeerJs-DG4hliKp.js.map} +1 -1
  44. package/dist/lib/index.js +21 -21
  45. package/dist/lib/{initControlPlane-BIrWmpzc.js → initControlPlane-DnpAzphg.js} +8 -8
  46. package/dist/lib/{initControlPlane-BIrWmpzc.js.map → initControlPlane-DnpAzphg.js.map} +1 -1
  47. package/dist/lib/{markdown-DEUoM1c6.js → markdown-DgdSt2GD.js} +2 -2
  48. package/dist/lib/{markdown-DEUoM1c6.js.map → markdown-DgdSt2GD.js.map} +1 -1
  49. package/dist/lib/{orchestrator-CFCW-l1O.js → orchestrator-CjEau_RG.js} +5 -5
  50. package/dist/lib/{orchestrator-CFCW-l1O.js.map → orchestrator-CjEau_RG.js.map} +1 -1
  51. package/dist/lib/{orchestrator-BtVj9bBB.js → orchestrator-ClXbJ4Mb.js} +2 -2
  52. package/dist/lib/{orchestrator-BtVj9bBB.js.map → orchestrator-ClXbJ4Mb.js.map} +1 -1
  53. package/dist/lib/{peerjs-BoBkx9DJ.js → peerjs-BdfpltIe.js} +3 -3
  54. package/dist/lib/{peerjs-BoBkx9DJ.js.map → peerjs-BdfpltIe.js.map} +1 -1
  55. package/dist/lib/{push-client-D6hbzDNV.js → push-client-DjwKknNg.js} +9 -3
  56. package/dist/lib/push-client-DjwKknNg.js.map +1 -0
  57. package/dist/lib/{shadow-claw-Cuf2O5bZ.js → shadow-claw-DcPGKenn.js} +4 -4
  58. package/dist/lib/{shadow-claw-Cuf2O5bZ.js.map → shadow-claw-DcPGKenn.js.map} +1 -1
  59. package/dist/lib/{shadow-claw-channel-config-BDPKt6Bd.js → shadow-claw-channel-config-DEL-aZ7N.js} +3 -3
  60. package/dist/lib/{shadow-claw-channel-config-BDPKt6Bd.js.map → shadow-claw-channel-config-DEL-aZ7N.js.map} +1 -1
  61. package/dist/lib/{shadow-claw-channels-BsP5JABM.js → shadow-claw-channels-BgaJiPdE.js} +2 -2
  62. package/dist/lib/{shadow-claw-channels-BsP5JABM.js.map → shadow-claw-channels-BgaJiPdE.js.map} +1 -1
  63. package/dist/lib/{shadow-claw-chat-Cam2lX4k.js → shadow-claw-chat-mXPQZbwT.js} +4 -4
  64. package/dist/lib/{shadow-claw-chat-Cam2lX4k.js.map → shadow-claw-chat-mXPQZbwT.js.map} +1 -1
  65. package/dist/lib/{shadow-claw-control-plane-DB2qcCFs.js → shadow-claw-control-plane-DO6foOLV.js} +6 -6
  66. package/dist/lib/{shadow-claw-control-plane-DB2qcCFs.js.map → shadow-claw-control-plane-DO6foOLV.js.map} +1 -1
  67. package/dist/lib/{shadow-claw-conversations-C9Vf4XGx.js → shadow-claw-conversations-BLwHTwdW.js} +2 -2
  68. package/dist/lib/{shadow-claw-conversations-C9Vf4XGx.js.map → shadow-claw-conversations-BLwHTwdW.js.map} +1 -1
  69. package/dist/lib/{shadow-claw-file-viewer-B4HYiI0v.js → shadow-claw-file-viewer-BSbZvSl-.js} +3 -3
  70. package/dist/lib/{shadow-claw-file-viewer-B4HYiI0v.js.map → shadow-claw-file-viewer-BSbZvSl-.js.map} +1 -1
  71. package/dist/lib/{shadow-claw-files-DVROUESO.js → shadow-claw-files-BLVr9Pua.js} +2 -2
  72. package/dist/lib/{shadow-claw-files-DVROUESO.js.map → shadow-claw-files-BLVr9Pua.js.map} +1 -1
  73. package/dist/lib/{shadow-claw-git-BqrO7cHJ.js → shadow-claw-git-CHC1H86J.js} +3 -3
  74. package/dist/lib/{shadow-claw-git-BqrO7cHJ.js.map → shadow-claw-git-CHC1H86J.js.map} +1 -1
  75. package/dist/lib/{shadow-claw-llm-BexUQdYo.js → shadow-claw-llm-CKXfxd3D.js} +2 -2
  76. package/dist/lib/{shadow-claw-llm-BexUQdYo.js.map → shadow-claw-llm-CKXfxd3D.js.map} +1 -1
  77. package/dist/lib/{shadow-claw-networking-CfXRGpUD.js → shadow-claw-networking-dpYvfchh.js} +2 -2
  78. package/dist/lib/{shadow-claw-networking-CfXRGpUD.js.map → shadow-claw-networking-dpYvfchh.js.map} +1 -1
  79. package/dist/lib/{shadow-claw-notifications-C9KPMgx_.js → shadow-claw-notifications-_YeAVPga.js} +2 -2
  80. package/dist/lib/{shadow-claw-notifications-C9KPMgx_.js.map → shadow-claw-notifications-_YeAVPga.js.map} +1 -1
  81. package/dist/lib/{shadow-claw-pages-CGzGXmrI.js → shadow-claw-pages-DVzZriZ7.js} +3 -3
  82. package/dist/lib/{shadow-claw-pages-CGzGXmrI.js.map → shadow-claw-pages-DVzZriZ7.js.map} +1 -1
  83. package/dist/lib/{shadow-claw-peerjs-B0ZB7yhB.js → shadow-claw-peerjs-BvixjeBz.js} +3 -3
  84. package/dist/lib/{shadow-claw-peerjs-B0ZB7yhB.js.map → shadow-claw-peerjs-BvixjeBz.js.map} +1 -1
  85. package/dist/lib/{shadow-claw-settings-DBfMZdDS.js → shadow-claw-settings-DV9-gD4Q.js} +2 -2
  86. package/dist/lib/{shadow-claw-settings-DBfMZdDS.js.map → shadow-claw-settings-DV9-gD4Q.js.map} +1 -1
  87. package/dist/lib/{shadow-claw-storage-CM-w6mgG.js → shadow-claw-storage-Bv__kXG6.js} +2 -2
  88. package/dist/lib/{shadow-claw-storage-CM-w6mgG.js.map → shadow-claw-storage-Bv__kXG6.js.map} +1 -1
  89. package/dist/lib/{shadow-claw-task-server-DV4b1g2-.js → shadow-claw-task-server-DNOe8PWK.js} +2 -2
  90. package/dist/lib/{shadow-claw-task-server-DV4b1g2-.js.map → shadow-claw-task-server-DNOe8PWK.js.map} +1 -1
  91. package/dist/lib/{shadow-claw-tasks-K-foOHSE.js → shadow-claw-tasks-qmcDT4PU.js} +3 -3
  92. package/dist/lib/{shadow-claw-tasks-K-foOHSE.js.map → shadow-claw-tasks-qmcDT4PU.js.map} +1 -1
  93. package/dist/lib/{shadow-claw-terminal-CpAknKyA.js → shadow-claw-terminal-0QUWP8of.js} +2 -2
  94. package/dist/lib/{shadow-claw-terminal-CpAknKyA.js.map → shadow-claw-terminal-0QUWP8of.js.map} +1 -1
  95. package/dist/lib/{shadow-claw-tools-D98XZ0Ep.js → shadow-claw-tools-Bs_bmI6-.js} +3 -3
  96. package/dist/lib/{shadow-claw-tools-D98XZ0Ep.js.map → shadow-claw-tools-Bs_bmI6-.js.map} +1 -1
  97. package/dist/lib/{shadow-claw-webvm-DLOb8dMI.js → shadow-claw-webvm-DYaoL7QY.js} +2 -2
  98. package/dist/lib/{shadow-claw-webvm-DLOb8dMI.js.map → shadow-claw-webvm-DYaoL7QY.js.map} +1 -1
  99. package/dist/lib/{syncWebMcpRegistration-NspKefOI.js → syncWebMcpRegistration-BhbqI6R2.js} +2 -2
  100. package/dist/lib/{syncWebMcpRegistration-NspKefOI.js.map → syncWebMcpRegistration-BhbqI6R2.js.map} +1 -1
  101. package/dist/public/.well-known/mcp/server-card.json +1 -1
  102. package/dist/public/.well-known/mcp.json +1 -1
  103. package/dist/public/AGENTS.md +22 -3
  104. package/dist/public/README.md +96 -278
  105. package/dist/public/agent.worker.js +50 -50
  106. package/dist/public/{base-task-model-CYkpwnvU-DDfHUUIS.js → base-task-model-CYkpwnvU-DrXr4G1R.js} +1 -1
  107. package/dist/public/{configurePeerJs-BhBokQlC.js → configurePeerJs-B470pdeU.js} +1 -1
  108. package/dist/public/docs/README.md +31 -31
  109. package/dist/public/docs/architecture/context-management.md +7 -0
  110. package/dist/public/docs/architecture/storage.md +10 -1
  111. package/dist/public/docs/decisions/bundled-typescript-architecture.md +0 -1
  112. package/dist/public/docs/decisions/native-web-components-and-signals.md +0 -1
  113. package/dist/public/docs/example/article/index.html +1 -1
  114. package/dist/public/docs/guides/publishing-to-github-pages.md +334 -0
  115. package/dist/public/docs/guides/server-development-configuration.md +15 -0
  116. package/dist/public/docs/publishing/index.html +1 -1
  117. package/dist/public/docs/skill-creator/index.html +1 -1
  118. package/dist/public/docs/subsystems/cli.md +10 -2
  119. package/dist/public/docs/subsystems/control-plane.md +9 -8
  120. package/dist/public/docs/subsystems/crypto.md +9 -0
  121. package/dist/public/docs/subsystems/custom-element-security.md +2 -0
  122. package/dist/public/docs/subsystems/mcp-server.md +31 -16
  123. package/dist/public/docs/subsystems/notifications.md +8 -5
  124. package/dist/public/docs/subsystems/providers.md +9 -0
  125. package/dist/public/docs/subsystems/share-target.md +107 -0
  126. package/dist/public/docs/subsystems/webmcp.md +43 -0
  127. package/dist/public/e2e/README.md +4 -2
  128. package/dist/public/{e2e-bridge-DcBoeFvd.js → e2e-bridge-CSW0S5Yz.js} +1 -1
  129. package/dist/public/{file-viewer-CKtVC9Ix.js → file-viewer-Cs-riNvy.js} +1 -1
  130. package/dist/public/{firebase-OSDX3viP.js → firebase-cwAl_ABm.js} +1 -1
  131. package/dist/public/{git-CkhCJonj.js → git-GD09WATp.js} +25 -25
  132. package/dist/public/index.html +1 -1
  133. package/dist/public/index.js +1 -1
  134. package/dist/public/{initControlPlane-Bi8ouFEb.js → initControlPlane-BqCY0NQy.js} +1 -1
  135. package/dist/public/initControlPlane-C-IXaKYX.js +1 -0
  136. package/dist/public/{language-detector-BRKrlra0.js → language-detector-CIeEuQFk.js} +1 -1
  137. package/dist/public/llms.txt +4 -2
  138. package/dist/public/main/index.html +1 -1
  139. package/dist/public/main/memory/index.html +1 -1
  140. package/dist/public/{markdown-C-hCVJtc.js → markdown-Cw7vCtTg.js} +1 -1
  141. package/dist/public/{openai-Bpd71Oja.js → openai-BJIb2c5j.js} +3 -3
  142. package/dist/public/{orchestrator-8qNGfw8r.js → orchestrator-BWkHHO9Y.js} +1 -1
  143. package/dist/public/{orchestrator-BT3Ks5ip.js → orchestrator-DpkkeD6T.js} +1 -1
  144. package/dist/public/{peerjs-DfhwEiiq.js → peerjs-tf2EmWjB.js} +1 -1
  145. package/dist/public/{prompt-api-polyfill-Dl1kbJhR.js → prompt-api-polyfill-CXd75leW.js} +1 -1
  146. package/dist/public/{push-client-BVLrE9pK.js → push-client-CHjSbrWs.js} +1 -1
  147. package/dist/public/{rewriter-j6D9LDER.js → rewriter-BEHn9gll.js} +1 -1
  148. package/dist/public/service-worker.js +1 -1
  149. package/dist/public/{shadow-claw-DCjxNDCX.js → shadow-claw-BBPV_9X8.js} +2 -2
  150. package/dist/public/{shadow-claw-channels-CITDrqMl.js → shadow-claw-channels-BJvrKQjg.js} +1 -1
  151. package/dist/public/{shadow-claw-chat-BQrSgSKF.js → shadow-claw-chat-BG2EaRaw.js} +1 -1
  152. package/dist/public/{shadow-claw-control-plane-Cp001OY0.js → shadow-claw-control-plane-BBa7WbTg.js} +2 -2
  153. package/dist/public/{shadow-claw-conversations-EhX4RvIj.js → shadow-claw-conversations-CFaWFmkm.js} +1 -1
  154. package/dist/public/{shadow-claw-file-viewer-DiQXIjZD.js → shadow-claw-file-viewer-Z-GIKaE9.js} +1 -1
  155. package/dist/public/{shadow-claw-files-lkTnSJsn.js → shadow-claw-files-BBx4iMsk.js} +1 -1
  156. package/dist/public/{shadow-claw-git-BlcA_z8x.js → shadow-claw-git-vTOKatwd.js} +2 -2
  157. package/dist/public/{shadow-claw-llm-CW16X9Rg.js → shadow-claw-llm-Ce1Ax6-x.js} +1 -1
  158. package/dist/public/{shadow-claw-networking-ySsMBY_i.js → shadow-claw-networking-q4XIzdB5.js} +1 -1
  159. package/dist/public/{shadow-claw-notifications-CyV2zCQ8.js → shadow-claw-notifications-D2cz0gmm.js} +1 -1
  160. package/dist/public/{shadow-claw-pages-Cx7cF9ah.js → shadow-claw-pages-3335lmSC.js} +1 -1
  161. package/dist/public/{shadow-claw-settings-zm4PHHGi.js → shadow-claw-settings-4ptYuJwO.js} +2 -2
  162. package/dist/public/{shadow-claw-storage-dy9JAEGd.js → shadow-claw-storage-bIrPKFhF.js} +1 -1
  163. package/dist/public/{shadow-claw-task-server-Bleww48s.js → shadow-claw-task-server-5yqVBxy1.js} +1 -1
  164. package/dist/public/{shadow-claw-tasks-CTI5oaLN.js → shadow-claw-tasks-DcqYHPLZ.js} +1 -1
  165. package/dist/public/{shadow-claw-terminal-mZ3i4HaQ.js → shadow-claw-terminal-fARaDONr.js} +1 -1
  166. package/dist/public/{shadow-claw-tools-B_nuk8Zl.js → shadow-claw-tools-paEAhcJr.js} +1 -1
  167. package/dist/public/{shadow-claw-webvm-Vl_jH5YL.js → shadow-claw-webvm-CbzH_vyX.js} +1 -1
  168. package/dist/public/{summarizer-VJNr-7xG.js → summarizer-BT3AuFed.js} +1 -1
  169. package/dist/public/{syncWebMcpRegistration-n2vAMJ4w.js → syncWebMcpRegistration-CWSYaHex.js} +1 -1
  170. package/dist/public/{transformers-BhQMWQG5.js → transformers-1VTxqKjT.js} +1 -1
  171. package/dist/public/{translator-DEQXb7ZL.js → translator-B50RYlFt.js} +1 -1
  172. package/dist/public/{webllm-CHN5H-Sy.js → webllm-FmyJWWpt.js} +1 -1
  173. package/dist/public/{writer-CD9oN2pq.js → writer-Bhb4YYRE.js} +1 -1
  174. package/dist/server.js +205 -203
  175. package/package.json +1 -1
  176. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +2 -0
  177. package/src/core/orchestrator/utils/configurePeerJs.test.ts +2 -0
  178. package/src/server/client-registry.test.ts +9 -0
  179. package/src/server/client-registry.ts +14 -0
  180. package/src/server/mcp/mcp-integration.test.ts +68 -16
  181. package/src/server/mcp/tools/built-in-tools.ts +125 -34
  182. package/src/server/mcp/tools/client-tool-relay.ts +81 -44
  183. package/src/server/mcp/tools/mcp-tools.test.ts +103 -13
  184. package/src/server/mcp/types.ts +3 -0
  185. package/src/server/openapi/paths/push.ts +48 -0
  186. package/src/server/server.ts +30 -0
  187. package/src/subsystems/notifications/push-client.test.ts +17 -0
  188. package/src/subsystems/notifications/push-client.ts +22 -2
  189. package/src/subsystems/notifications/push-routes.test.ts +31 -0
  190. package/src/subsystems/notifications/push-routes.ts +10 -0
  191. package/src/subsystems/notifications/push-store.test.ts +49 -0
  192. package/src/subsystems/notifications/push-store.ts +45 -0
  193. package/src/worker/worker.test.ts +2 -0
  194. package/dist/lib/push-client-D6hbzDNV.js.map +0 -1
@@ -1 +1 @@
1
- var e=class{#e;#t;#n=!1;#r=new Set;#i=new AbortController;#a=null;constructor(e,t){this.#e=e,this.#t=t}static _validateLanguageTag(e){try{return Intl.getCanonicalLocales(e)[0]}catch{throw RangeError(`Invalid language tag: ${e}`)}}static _checkContext(){let e=this.__window||(typeof globalThis<`u`?globalThis:null),t=!1;try{(!e||e.closed||e.document&&e.document.defaultView!==e)&&(t=!0)}catch{t=!0}if(t){let t;try{t=e?.DOMException||globalThis.DOMException||Error}catch{t=globalThis.DOMException||Error}throw new t(`The execution context is not valid.`,`InvalidStateError`)}}_checkContext(){this.constructor._checkContext()}static baseAvailability(e={}){try{this._checkContext()}catch(e){let t=Promise.reject(e);return t.catch(()=>{}),t}let t=(async()=>{await this.ensureLanguageModel();let t={expectedInputs:[{type:`text`,languages:e.expectedInputLanguages||[`en`]}],expectedOutputs:[{type:`text`,languages:e.outputLanguage?[e.outputLanguage]:[`en`]}]};return await(this.__window||globalThis).LanguageModel.availability(t)})();return t.catch(()=>{}),t}static async ensureLanguageModel(){let e=this.__window||globalThis;e!==void 0&&!e.LanguageModel&&await import(`./prompt-api-polyfill-Dl1kbJhR.js`)}static availability(e={}){let t=(async()=>{this._checkContext(),await this.ensureLanguageModel();let t={expectedInputs:[{type:`text`,languages:e.expectedInputLanguages||[`en`]}],expectedOutputs:[{type:`text`,languages:e.outputLanguage?[e.outputLanguage]:[`en`]}]};return await globalThis.LanguageModel.availability(t)})();return t.catch(()=>{}),t}_isNonTranslatable(e){return typeof e==`string`&&/^[\s\x00-\x1f]*$/.test(e)}_runTask(e,t={}){if(this._isNonTranslatable(e)){let t=Promise.resolve(e);return t.catch(()=>{}),t}let n=this._runTaskInternal(e,t);return n.catch(()=>{}),n}async _runTaskInternal(e,t={}){if(this._checkContext(),this.#n){let e=Promise.reject(this.#a||new DOMException(`The summarizer has been destroyed.`,`AbortError`));return e.catch(()=>{}),e}let{userPrompt:n}=this.#t.buildPrompt(e,t),r=AbortSignal.any([this.#i.signal,t.signal].filter(Boolean));if(r.aborted){let e=Promise.reject(r.reason||new DOMException(`Aborted`,`AbortError`));return e.catch(()=>{}),e}let i={...t,signal:r},a=await this.#e.clone(i);this.#r.add(a);try{return await new Promise((e,t)=>{let o=()=>{t(r.reason||new DOMException(`Aborted`,`AbortError`))};if(r.aborted){o();return}r.addEventListener(`abort`,o,{once:!0}),a.prompt(n,i).then(e).catch(t).finally(()=>{r.removeEventListener(`abort`,o)})})}finally{a.destroy(),this.#r.delete(a)}}_runTaskStreaming(e,t={}){if(this._checkContext(),this._isNonTranslatable(e))return new ReadableStream({start(t){t.enqueue(e),t.close()}});let{userPrompt:n}=this.#t.buildPrompt(e,t),r=this.#e,i=t.signal;if(i?.aborted)throw i.reason||new DOMException(`Aborted`,`AbortError`);let a=this,o=AbortSignal.any([this.#i.signal,t.signal].filter(Boolean));if(o.aborted)throw o.reason||new DOMException(`Aborted`,`AbortError`);return new ReadableStream({async start(e){if(a.#n){e.error(a.#a||new DOMException(`The summarizer has been destroyed.`,`AbortError`));return}let i,s,c=()=>{s&&=(s.cancel().catch(()=>{}),null),i&&=(i.destroy(),a.#r.delete(i),null)},l=()=>{i&&c();try{e.error(o.reason||new DOMException(`Aborted`,`AbortError`))}catch{}};o.addEventListener(`abort`,l,{once:!0});try{let c={...t,signal:o};if(i=await r.clone(c),a.#r.add(i),o.aborted){l();return}for(s=i.promptStreaming(n,c).getReader();;){let{done:t,value:n}=await s.read();if(t)break;e.enqueue(n)}e.close()}catch(t){e.error(t)}finally{o.removeEventListener(`abort`,l),c()}}})}measureInputUsage(e,t={}){if(this._checkContext(),this.#n){let e=Promise.reject(this.#a||new DOMException(`The summarizer has been destroyed.`,`AbortError`));return e.catch(()=>{}),e}let n=AbortSignal.any([this.#i.signal,t.signal].filter(Boolean));if(n.aborted){let e=Promise.reject(n.reason||new DOMException(`Aborted`,`AbortError`));return e.catch(()=>{}),e}let r=new Promise((t,r)=>{let i=()=>r(n.reason||new DOMException(`Aborted`,`AbortError`));n.addEventListener(`abort`,i,{once:!0}),this.#e.measureInputUsage(e).then(t).catch(r).finally(()=>{n.removeEventListener(`abort`,i)})});return r.catch(()=>{}),r}get inputQuota(){return this.#e.inputQuota}destroy(e){if(!this.#n){this.#n=!0,this.#a=e||new DOMException(`The summarizer has been destroyed.`,`AbortError`),this.#i.abort(this.#a);for(let e of this.#r)e.destroy();this.#r.clear(),this.#e.destroy()}}static exposeAPIGlobally(e,t,n){if(typeof globalThis>`u`||!globalThis.document)return;let r=!!globalThis[n],i=n=>{try{if(!n||n[e]&&n[e].__isPolyfill)return;if(!(e in n)||r){let r={[e]:class extends t{}}[e];r.prototype[Symbol.toStringTag]=e,r.__window=n,r.__isPolyfill=!0,typeof r.create==`function`&&(r.create=r.create.bind(r)),typeof r.availability==`function`&&(r.availability=r.availability.bind(r)),Object.defineProperty(n,e,{value:r,writable:!0,configurable:!0,enumerable:!1}),n.DOMException&&(n.QuotaExceededError=n.DOMException)}}catch{}};if(i(globalThis),typeof HTMLIFrameElement<`u`)try{let e=Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype,`contentWindow`);e&&e.get&&Object.defineProperty(HTMLIFrameElement.prototype,"contentWindow",{get(){let t=e.get.call(this);return t&&i(t),t},configurable:!0})}catch{}let a=new MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e.tagName===`IFRAME`&&(i(e.contentWindow),e.addEventListener(`load`,()=>i(e.contentWindow),{once:!1}))});globalThis.document?.documentElement&&(a.observe(globalThis.document.documentElement,{childList:!0,subtree:!0}),globalThis.document.querySelectorAll(`iframe`).forEach(e=>{i(e.contentWindow),e.addEventListener(`load`,()=>i(e.contentWindow),{once:!1})})),globalThis[e]&&globalThis[e].__isPolyfill&&console.log(`Polyfill: window.${e} is now backed by the ${e} API polyfill.`)}};export{e as t};
1
+ var e=class{#e;#t;#n=!1;#r=new Set;#i=new AbortController;#a=null;constructor(e,t){this.#e=e,this.#t=t}static _validateLanguageTag(e){try{return Intl.getCanonicalLocales(e)[0]}catch{throw RangeError(`Invalid language tag: ${e}`)}}static _checkContext(){let e=this.__window||(typeof globalThis<`u`?globalThis:null),t=!1;try{(!e||e.closed||e.document&&e.document.defaultView!==e)&&(t=!0)}catch{t=!0}if(t){let t;try{t=e?.DOMException||globalThis.DOMException||Error}catch{t=globalThis.DOMException||Error}throw new t(`The execution context is not valid.`,`InvalidStateError`)}}_checkContext(){this.constructor._checkContext()}static baseAvailability(e={}){try{this._checkContext()}catch(e){let t=Promise.reject(e);return t.catch(()=>{}),t}let t=(async()=>{await this.ensureLanguageModel();let t={expectedInputs:[{type:`text`,languages:e.expectedInputLanguages||[`en`]}],expectedOutputs:[{type:`text`,languages:e.outputLanguage?[e.outputLanguage]:[`en`]}]};return await(this.__window||globalThis).LanguageModel.availability(t)})();return t.catch(()=>{}),t}static async ensureLanguageModel(){let e=this.__window||globalThis;e!==void 0&&!e.LanguageModel&&await import(`./prompt-api-polyfill-CXd75leW.js`)}static availability(e={}){let t=(async()=>{this._checkContext(),await this.ensureLanguageModel();let t={expectedInputs:[{type:`text`,languages:e.expectedInputLanguages||[`en`]}],expectedOutputs:[{type:`text`,languages:e.outputLanguage?[e.outputLanguage]:[`en`]}]};return await globalThis.LanguageModel.availability(t)})();return t.catch(()=>{}),t}_isNonTranslatable(e){return typeof e==`string`&&/^[\s\x00-\x1f]*$/.test(e)}_runTask(e,t={}){if(this._isNonTranslatable(e)){let t=Promise.resolve(e);return t.catch(()=>{}),t}let n=this._runTaskInternal(e,t);return n.catch(()=>{}),n}async _runTaskInternal(e,t={}){if(this._checkContext(),this.#n){let e=Promise.reject(this.#a||new DOMException(`The summarizer has been destroyed.`,`AbortError`));return e.catch(()=>{}),e}let{userPrompt:n}=this.#t.buildPrompt(e,t),r=AbortSignal.any([this.#i.signal,t.signal].filter(Boolean));if(r.aborted){let e=Promise.reject(r.reason||new DOMException(`Aborted`,`AbortError`));return e.catch(()=>{}),e}let i={...t,signal:r},a=await this.#e.clone(i);this.#r.add(a);try{return await new Promise((e,t)=>{let o=()=>{t(r.reason||new DOMException(`Aborted`,`AbortError`))};if(r.aborted){o();return}r.addEventListener(`abort`,o,{once:!0}),a.prompt(n,i).then(e).catch(t).finally(()=>{r.removeEventListener(`abort`,o)})})}finally{a.destroy(),this.#r.delete(a)}}_runTaskStreaming(e,t={}){if(this._checkContext(),this._isNonTranslatable(e))return new ReadableStream({start(t){t.enqueue(e),t.close()}});let{userPrompt:n}=this.#t.buildPrompt(e,t),r=this.#e,i=t.signal;if(i?.aborted)throw i.reason||new DOMException(`Aborted`,`AbortError`);let a=this,o=AbortSignal.any([this.#i.signal,t.signal].filter(Boolean));if(o.aborted)throw o.reason||new DOMException(`Aborted`,`AbortError`);return new ReadableStream({async start(e){if(a.#n){e.error(a.#a||new DOMException(`The summarizer has been destroyed.`,`AbortError`));return}let i,s,c=()=>{s&&=(s.cancel().catch(()=>{}),null),i&&=(i.destroy(),a.#r.delete(i),null)},l=()=>{i&&c();try{e.error(o.reason||new DOMException(`Aborted`,`AbortError`))}catch{}};o.addEventListener(`abort`,l,{once:!0});try{let c={...t,signal:o};if(i=await r.clone(c),a.#r.add(i),o.aborted){l();return}for(s=i.promptStreaming(n,c).getReader();;){let{done:t,value:n}=await s.read();if(t)break;e.enqueue(n)}e.close()}catch(t){e.error(t)}finally{o.removeEventListener(`abort`,l),c()}}})}measureInputUsage(e,t={}){if(this._checkContext(),this.#n){let e=Promise.reject(this.#a||new DOMException(`The summarizer has been destroyed.`,`AbortError`));return e.catch(()=>{}),e}let n=AbortSignal.any([this.#i.signal,t.signal].filter(Boolean));if(n.aborted){let e=Promise.reject(n.reason||new DOMException(`Aborted`,`AbortError`));return e.catch(()=>{}),e}let r=new Promise((t,r)=>{let i=()=>r(n.reason||new DOMException(`Aborted`,`AbortError`));n.addEventListener(`abort`,i,{once:!0}),this.#e.measureInputUsage(e).then(t).catch(r).finally(()=>{n.removeEventListener(`abort`,i)})});return r.catch(()=>{}),r}get inputQuota(){return this.#e.inputQuota}destroy(e){if(!this.#n){this.#n=!0,this.#a=e||new DOMException(`The summarizer has been destroyed.`,`AbortError`),this.#i.abort(this.#a);for(let e of this.#r)e.destroy();this.#r.clear(),this.#e.destroy()}}static exposeAPIGlobally(e,t,n){if(typeof globalThis>`u`||!globalThis.document)return;let r=!!globalThis[n],i=n=>{try{if(!n||n[e]&&n[e].__isPolyfill)return;if(!(e in n)||r){let r={[e]:class extends t{}}[e];r.prototype[Symbol.toStringTag]=e,r.__window=n,r.__isPolyfill=!0,typeof r.create==`function`&&(r.create=r.create.bind(r)),typeof r.availability==`function`&&(r.availability=r.availability.bind(r)),Object.defineProperty(n,e,{value:r,writable:!0,configurable:!0,enumerable:!1}),n.DOMException&&(n.QuotaExceededError=n.DOMException)}}catch{}};if(i(globalThis),typeof HTMLIFrameElement<`u`)try{let e=Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype,`contentWindow`);e&&e.get&&Object.defineProperty(HTMLIFrameElement.prototype,"contentWindow",{get(){let t=e.get.call(this);return t&&i(t),t},configurable:!0})}catch{}let a=new MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e.tagName===`IFRAME`&&(i(e.contentWindow),e.addEventListener(`load`,()=>i(e.contentWindow),{once:!1}))});globalThis.document?.documentElement&&(a.observe(globalThis.document.documentElement,{childList:!0,subtree:!0}),globalThis.document.querySelectorAll(`iframe`).forEach(e=>{i(e.contentWindow),e.addEventListener(`load`,()=>i(e.contentWindow),{once:!1})})),globalThis[e]&&globalThis[e].__isPolyfill&&console.log(`Polyfill: window.${e} is now backed by the ${e} API polyfill.`)}};export{e as t};
@@ -1 +1 @@
1
- import{r as e}from"./config-CrHchneq.js";import{Ct as t,ht as n}from"./orchestrator-BT3Ks5ip.js";import{t as r}from"./setConfig-DFMYnYLE.js";async function i(i,a,o,s,c=``,l=0,u=``,d=!0){let f=o.trim(),p=t(s),m=c.trim(),h=Number.isFinite(l)?Math.max(0,Math.floor(l)):0,g=u.trim(),_=!!d;i.peerjsMyPeerId=f,i.peerjsTrustedPeerIds=p,i.peerjsServerHost=m,i.peerjsServerPort=h,i.peerjsServerPath=g,i.peerjsServerSecure=_,await r(a,e.PEERJS_MY_PEER_ID,f),await r(a,e.PEERJS_TRUSTED_PEER_IDS,JSON.stringify(p)),await r(a,e.PEERJS_SERVER_HOST,m),await r(a,e.PEERJS_SERVER_PORT,h?String(h):``),await r(a,e.PEERJS_SERVER_PATH,g),await r(a,e.PEERJS_SERVER_SECURE,_?`true`:`false`);let v=m?{host:m,path:g||void 0,port:h||void 0,secure:_}:{};i.peerjs.stop(),i.peerjs.configure(f,p,v),f&&n(i,`peerjs`)&&i.peerjs.start()}export{i as t};
1
+ import{r as e}from"./config-CrHchneq.js";import{Ct as t,ht as n}from"./orchestrator-DpkkeD6T.js";import{t as r}from"./setConfig-DFMYnYLE.js";async function i(i,a,o,s,c=``,l=0,u=``,d=!0){let f=o.trim(),p=t(s),m=c.trim(),h=Number.isFinite(l)?Math.max(0,Math.floor(l)):0,g=u.trim(),_=!!d;i.peerjsMyPeerId=f,i.peerjsTrustedPeerIds=p,i.peerjsServerHost=m,i.peerjsServerPort=h,i.peerjsServerPath=g,i.peerjsServerSecure=_,await r(a,e.PEERJS_MY_PEER_ID,f),await r(a,e.PEERJS_TRUSTED_PEER_IDS,JSON.stringify(p)),await r(a,e.PEERJS_SERVER_HOST,m),await r(a,e.PEERJS_SERVER_PORT,h?String(h):``),await r(a,e.PEERJS_SERVER_PATH,g),await r(a,e.PEERJS_SERVER_SECURE,_?`true`:`false`);let v=m?{host:m,path:g||void 0,port:h||void 0,secure:_}:{};i.peerjs.stop(),i.peerjs.configure(f,p,v),f&&n(i,`peerjs`)&&i.peerjs.start()}export{i as t};
@@ -21,37 +21,36 @@ How the core pieces fit together.
21
21
 
22
22
  Detailed docs for each major subsystem.
23
23
 
24
- | Document | What it covers |
25
- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
- | [Shell Emulator](subsystems/shell.md) | JS shell via `just-bash` AST evaluation, OPFS bridge, supported commands |
27
- | [WebVM](subsystems/vm.md) | v86 Alpine Linux, boot modes, exclusivity guard, terminal bridge, 9p sync |
28
- | [Git Integration](subsystems/git.md) | isomorphic-git on native filesystem handles, merge conflicts, credentials |
29
- | [Channel System](subsystems/channels.md) | Channel registry, browser/Telegram/iMessage channels, router, multi-channel flow |
30
- | [Remote MCP](subsystems/remote-mcp.md) | External MCP servers, tool discovery, authentication, JSON-RPC protocol, OAuth reconnection |
31
- | [Accounts & Credentials](subsystems/accounts.md) | Service account management, credential storage, auth bridges |
32
- | [Crypto & Secrets](subsystems/crypto.md) | AES-256-GCM key storage, runtime key handling, environment hardening; idempotent default Trusted Types policy |
33
- | [Tools & Profiles](subsystems/tools.md) | Tool definitions, execution dispatch, profiles, tool configuration panel, adding new tools |
34
- | [Agent Skills](subsystems/skills.md) | Workspace-local skill discovery, slash-command routing, declarative tool execution pipelines, output/toast suppression, discovery index generation (.well-known/agent-skills/index.json), resources, and static publishing |
35
- | [Notifications & Scheduling](subsystems/notifications.md) | Web Push, VAPID, server-side SQLite scheduler, recursion guards |
36
- | [Providers & Rate Limiting](subsystems/providers.md) | LLM provider registry, adapter pattern, Prompt API (default), dynamic CPU/WASM fallback, CacheStorage model downloads, and 30s auto-closing error dialogs |
37
- | [Electron Desktop](subsystems/electron.md) | Desktop app architecture, in-process server, power management |
38
- | [Reactive UI & Component Library](subsystems/reactive-ui.md) | Signals, `ShadowClawElement`, `reconcileList`, Web Components, stores, ESM package exports, and Storybook workbench |
39
- | [Model Registry & Capabilities](subsystems/providers.md#model-registry) | Dynamic model metadata fetching and modality capability detection |
40
- | [Attachment Capabilities](subsystems/attachment-capabilities.md) | MIME-aware attachment handling and native vs fallback delivery |
41
- | [Chat Template Sanitizer](subsystems/sanitizer.md) | Strip control tokens and structural markers from local model output |
42
- | [A2UI Interactive Surfaces](subsystems/a2ui.md) | A2UI v1.0 catalog renderer, component rendering, PeerJS surface delivery, data binding, and Storybook workbench coverage for all 18 components |
43
- | [AGUI Events & Adapter](subsystems/agui.md) | Translates orchestrator EventBus events to standardized AG-UI protocol events for UI visibility |
44
- | [Trusted Types Tinyfill](subsystems/trusted-types-tinyfill.md) | Polyfill for Trusted Types API, browser compatibility, security rationale |
45
- | [Email Integration](subsystems/email.md) | IMAP/SMTP support with encrypted credentials |
46
- | [WebMCP](subsystems/webmcp.md) | Browser's Model Context Protocol integration and tool execution, supporting Chrome 154+ object input schemas, getWebMcpTools querying, and graceful degradation |
47
- | [Pages System](subsystems/pages.md) | Workspace-relative pages rendering, sidebar navigation, static site seeding, pretty paths, same-origin route validation, and production asset inlining |
48
- | [Custom Element Security](subsystems/custom-element-security.md) | Custom element registry & DOM guards, allowlists, script descriptors ({ src, hasInit }), iframe sandbox policies, and nonce-gated CSP |
49
- | [LiteRT Provider](subsystems/providers.md) | In-browser WebGPU inference provider using Google LiteRT-LM |
50
- | [CLI & Static Site Publishing](subsystems/cli.md) | `shadow-claw` CLI commands (`build`, `dev`, `serve`, `server`, `init`, `clients`, `send`, `backup`, `tasks`, `mcp`, `skills:index`), dual-root path resolution, and npm packaging |
51
- | [Control Plane & Client Bridge](subsystems/control-plane.md) | Dual SSE/WebSocket control plane for CLI driving and persistent execution surface |
52
- | [Stateless MCP Server](subsystems/mcp-server.md) | Official Stateless Model Context Protocol (2026-07-28) server for external agent hosts, multi-client tool targeting, CLI driving, and dynamic browser tool relay |
53
- | [File Backup Subsystem](subsystems/backup.md) | Remote workspace and OPFS file backups to server storage |
54
- | [OpenAPI & Endpoint Discoverability](subsystems/openapi.md) | Official OpenAPI 3.1 specification, interactive Scalar documentation UI, and route-coverage contract testing |
24
+ | Document | What it covers |
25
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
+ | [Shell Emulator](subsystems/shell.md) | JS shell via `just-bash` AST evaluation, OPFS bridge, supported commands |
27
+ | [WebVM](subsystems/vm.md) | v86 Alpine Linux, boot modes, exclusivity guard, terminal bridge, 9p sync |
28
+ | [Git Integration](subsystems/git.md) | isomorphic-git on native filesystem handles, merge conflicts, credentials |
29
+ | [Channel System](subsystems/channels.md) | Channel registry, browser/Telegram/iMessage channels, router, multi-channel flow |
30
+ | [Remote MCP](subsystems/remote-mcp.md) | External MCP servers, tool discovery, authentication, JSON-RPC protocol, OAuth reconnection |
31
+ | [Accounts & Credentials](subsystems/accounts.md) | Service account management, credential storage, auth bridges |
32
+ | [Crypto & Secrets](subsystems/crypto.md) | AES-256-GCM key storage, runtime key handling, environment hardening; idempotent default Trusted Types policy |
33
+ | [Tools & Profiles](subsystems/tools.md) | Tool definitions, execution dispatch, profiles, tool configuration panel, adding new tools |
34
+ | [Agent Skills](subsystems/skills.md) | Workspace-local skill discovery, slash-command routing, declarative tool execution pipelines, output/toast suppression, discovery index generation (.well-known/agent-skills/index.json), resources, and static publishing |
35
+ | [Notifications & Scheduling](subsystems/notifications.md) | Web Push, VAPID, server-side SQLite scheduler, recursion guards |
36
+ | [Providers & Model Registry](subsystems/providers.md) | LLM provider registry, adapter pattern, Prompt API (default with polyfills), LiteRT-LM WebGPU, dynamic CPU/WASM fallback, model registry & capability detection, CacheStorage model downloads, and 30s auto-closing dialogs |
37
+ | [Electron Desktop](subsystems/electron.md) | Desktop app architecture, in-process server, power management |
38
+ | [Reactive UI & Component Library](subsystems/reactive-ui.md) | Signals, `ShadowClawElement`, `reconcileList`, Web Components, stores, ESM package exports, and Storybook workbench |
39
+ | [Attachment Capabilities](subsystems/attachment-capabilities.md) | MIME-aware attachment handling and native vs fallback delivery |
40
+ | [Chat Template Sanitizer](subsystems/sanitizer.md) | Strip control tokens and structural markers from local model output |
41
+ | [A2UI Interactive Surfaces](subsystems/a2ui.md) | A2UI v1.0 catalog renderer, component rendering, PeerJS surface delivery, data binding, and Storybook workbench coverage for all 18 components |
42
+ | [AGUI Events & Adapter](subsystems/agui.md) | Translates orchestrator EventBus events to standardized AG-UI protocol events for UI visibility |
43
+ | [Trusted Types Tinyfill](subsystems/trusted-types-tinyfill.md) | Polyfill for Trusted Types API, browser compatibility, security rationale |
44
+ | [Email Integration](subsystems/email.md) | IMAP/SMTP support with encrypted credentials |
45
+ | [WebMCP](subsystems/webmcp.md) | Browser's Model Context Protocol integration and tool execution, supporting Chrome 154+ object input schemas, getWebMcpTools querying, and graceful degradation |
46
+ | [Web Share Target](subsystems/share-target.md) | OS share sheet integration receiving files, URLs, and text into OPFS workspaces via PWA Web Share Target API |
47
+ | [Pages System](subsystems/pages.md) | Workspace-relative pages rendering, sidebar navigation, static site seeding, pretty paths, same-origin route validation, and production asset inlining |
48
+ | [Custom Element Security](subsystems/custom-element-security.md) | Custom element registry & DOM guards, allowlists, script descriptors ({ src, hasInit }), iframe sandbox policies, and nonce-gated CSP |
49
+ | [CLI & Static Site Publishing](subsystems/cli.md) | `shadow-claw` CLI commands (`build`, `dev`, `serve`, `server`, `init`, `clients`, `send`, `backup`, `tasks`, `mcp`, `skills:index`), dual-root path resolution, and npm packaging |
50
+ | [Control Plane & Client Bridge](subsystems/control-plane.md) | SSE, WebSocket, and WebRTC DataChannel control plane for CLI driving, headless automation, and persistent browser execution surfaces |
51
+ | [Stateless MCP Server](subsystems/mcp-server.md) | Official Stateless Model Context Protocol (2026-07-28) server for external agent hosts, featuring native server management tools (`shadowclaw_server_*`), multi-client tool targeting, and dynamic browser tool relaying |
52
+ | [File Backup Subsystem](subsystems/backup.md) | Remote workspace and OPFS file backups to server storage |
53
+ | [OpenAPI & Endpoint Discoverability](subsystems/openapi.md) | Official OpenAPI 3.1 specification, interactive Scalar documentation UI, and route-coverage contract testing |
55
54
 
56
55
  ### Guides
57
56
 
@@ -68,6 +67,7 @@ Step-by-step instructions for common dev tasks.
68
67
  | [Service Accounts & Credentials](guides/adding-service-accounts.md) | How to manage encrypted credentials for channels and services |
69
68
  | [Configuring Messaging Channels](guides/configuring-messaging-channels.md) | User guide for Telegram and iMessage setup |
70
69
  | [Server Development Configuration](guides/server-development-configuration.md) | CLI flags, CORS modes, host binding, port configuration, opt-in HTTPS/TLS, CSP report logging |
70
+ | [Publishing to GitHub Pages](guides/publishing-to-github-pages.md) | Step-by-step setup for static publishing via GitHub Actions, CLI builds, and template repos |
71
71
 
72
72
  ### Decisions
73
73
 
@@ -65,6 +65,13 @@ export function estimateMessageTokens(message: ConversationMessage): number {
65
65
  }
66
66
  ```
67
67
 
68
+ ### Token Estimation & Cache Tracking
69
+
70
+ In addition to estimating context consumption before LLM invocation, ShadowClaw tracks live token metrics and prompt caching statistics returned by providers:
71
+
72
+ - **Token Usage Tracking:** Tracks prompt/input tokens, output/completion tokens, and total token usage per turn.
73
+ - **Cache Hits & Misses:** Captures cache read hits (`cache_read_input_tokens`) and cache creation (`cache_creation_input_tokens`) from Anthropic and AWS Bedrock responses, accumulating them in `StreamAccumulator` (`src/worker/StreamAccumulator/`) and recording them in conversation history.
74
+
68
75
  ## Output Truncation
69
76
 
70
77
  **File:** `src/context/truncateToolOutput.ts`
@@ -217,6 +217,15 @@ To prevent storage state leakage when multiple instances are deployed under subp
217
217
  When booting a namespaced deployment for the first time, ShadowClaw checks if data was previously stored under `"shadowclaw"`.
218
218
  - **IndexedDB**: Copies all object stores into the new namespaced database in a single non-destructive pass, setting `DB_MIGRATED_FROM_LEGACY`.
219
219
  - **OPFS**: Recursively copies all legacy workspace directories and files from `"shadowclaw"` into `"shadowclaw-${namespace}"` (with Safari worker write fallback), setting `OPFS_MIGRATED_FROM_LEGACY`.
220
- The legacy storage is left intact as a fallback seed.
220
+ The legacy storage is left intact as a fallback seed.
221
221
  5. **Namespaced `localStorage` (`namespacedStorage.ts`):**
222
222
  Key pattern `shadowclaw:${namespace}:${key}` with automatic one-time copy-on-read from legacy unprefixed keys.
223
+
224
+ ## Files Browser Safeguards & Clipboard Operations
225
+
226
+ The Files browser (`src/components/shadow-claw-files/`) incorporates robust safeguards for file and directory manipulation:
227
+
228
+ - **Clipboard-Driven Actions**: Supports Cut, Copy, and Paste for files and directories across workspaces. The Paste action is dynamically hidden when the clipboard is empty.
229
+ - **Folder Self-Paste Protection**: When copying or moving directories, operations recursively validate target paths, strictly preventing pasting a directory into itself or any of its descendants.
230
+ - **Inter-Group Transfers**: Files and folders can be moved or copied across different conversation groups (`sourceGroupId` $\rightarrow$ `targetGroupId`).
231
+ - **Conflict Resolution**: When an item with the same name exists at the destination, the UI prompts with non-destructive options to either rename the incoming item or overwrite the existing target.
@@ -2,7 +2,6 @@
2
2
 
3
3
  **Status:** Active
4
4
  **Date:** 2026-04-19
5
- **Commit:** `7d0540e` — "Build pipeline overhaul: TypeScript, Rollup, Electron, and E2E improvements"
6
5
 
7
6
  ## Context
8
7
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  **Status:** Active
4
4
  **Date:** Early project — established in original architecture
5
- **Commit:** `7d0540e` — co-located component assets introduced
6
5
 
7
6
  ## Context
8
7
 
@@ -1,4 +1,4 @@
1
- <!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="19ae0bb4c7c6a8640bebeb5c18f31bda04741ae3" /><meta name="version" content="1.28.0" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><meta name="description" content="ShadowClaw - Browser-native AI agent." /><link rel="canonical" href="https://xt-ml.github.io/shadow-claw/"><meta property="og:title" content="ShadowClaw"><meta property="og:description" content="Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution."><meta property="og:type" content="website"><meta property="og:url" content="https://xt-ml.github.io/shadow-claw/"><meta property="og:image" content="https://xt-ml.github.io/shadow-claw/assets/icons/512.png"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="ShadowClaw"><meta name="twitter:description" content="Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution."><meta name="twitter:image" content="https://xt-ml.github.io/shadow-claw/assets/icons/512.png"><script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"SoftwareApplication","@id":"https://xt-ml.github.io/shadow-claw/#software","name":"ShadowClaw","description":"Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution.","url":"https://xt-ml.github.io/shadow-claw/","applicationCategory":"DeveloperApplication","operatingSystem":"Web, Browser, Desktop","offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"author":{"@type":"Organization","name":"xt-ml","url":"https://github.com/xt-ml"},"codeRepository":"https://github.com/xt-ml/shadow-claw","license":"https://github.com/xt-ml/shadow-claw/blob/main/LICENSE"},{"@type":"WebSite","@id":"https://xt-ml.github.io/shadow-claw/#website","url":"https://xt-ml.github.io/shadow-claw/","name":"ShadowClaw","description":"Official website and live deployment of ShadowClaw browser-native AI assistant.","publisher":{"@type":"Organization","name":"xt-ml","url":"https://github.com/xt-ml"}},{"@type":"Organization","@id":"https://xt-ml.github.io/shadow-claw/#organization","name":"xt-ml","url":"https://github.com/xt-ml","sameAs":["https://github.com/xt-ml"]}]}</script><meta name="theme-color" content="#fff" /><meta http-equiv="origin-trial" content="A88uTgESYWcVgREKVReKB6jom41uP8TzW6ei3jNdidf+Xl6ONqKRjKNBphsxhrZmdkakLK3oXDgkGq53rtZYSAMAAAB2eyJvcmlnaW4iOiJodHRwczovL3h0LW1sLmdpdGh1Yi5pbzo0NDMiLCJmZWF0dXJlIjoiV2ViTUNQIiwiZXhwaXJ5IjoxNzk0ODczNjAwLCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ=="><base href="/shadow-claw/"><script id="shadow-claw-site-config" type="application/json">{"$schema":"https:\u002f\u002fjson-schema.org\u002fdraft\u002f2020-12\u002fschema","site":{"title":"ShadowClaw","description":"ShadowClaw - Browser-native AI agent.","themeColor":"#fff"},"branding":{"faviconPath":"assets\u002ficons\u002ffavicon.ico","appleTouchIconPath":"assets\u002ficons\u002f180.png","notFoundPath":"404.html"},"pwa":{"manifestPath":"manifest.json","themeColor":"#000000"},"sitemapPath":"sitemap.xml","assets":["assets"],"sidebar":{"pagesHidden":false,"chatHidden":false,"tasksHidden":false,"filesHidden":false,"defaultPage":"pages"},"settings":{"defaultToolsProfile":"__builtin_default"}}</script>
1
+ <!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="6c79eccd1cf4132f8ef7e80b4310efd1b42ce4cc" /><meta name="version" content="1.29.0" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><meta name="description" content="ShadowClaw - Browser-native AI agent." /><link rel="canonical" href="https://xt-ml.github.io/shadow-claw/"><meta property="og:title" content="ShadowClaw"><meta property="og:description" content="Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution."><meta property="og:type" content="website"><meta property="og:url" content="https://xt-ml.github.io/shadow-claw/"><meta property="og:image" content="https://xt-ml.github.io/shadow-claw/assets/icons/512.png"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="ShadowClaw"><meta name="twitter:description" content="Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution."><meta name="twitter:image" content="https://xt-ml.github.io/shadow-claw/assets/icons/512.png"><script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"SoftwareApplication","@id":"https://xt-ml.github.io/shadow-claw/#software","name":"ShadowClaw","description":"Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution.","url":"https://xt-ml.github.io/shadow-claw/","applicationCategory":"DeveloperApplication","operatingSystem":"Web, Browser, Desktop","offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"author":{"@type":"Organization","name":"xt-ml","url":"https://github.com/xt-ml"},"codeRepository":"https://github.com/xt-ml/shadow-claw","license":"https://github.com/xt-ml/shadow-claw/blob/main/LICENSE"},{"@type":"WebSite","@id":"https://xt-ml.github.io/shadow-claw/#website","url":"https://xt-ml.github.io/shadow-claw/","name":"ShadowClaw","description":"Official website and live deployment of ShadowClaw browser-native AI assistant.","publisher":{"@type":"Organization","name":"xt-ml","url":"https://github.com/xt-ml"}},{"@type":"Organization","@id":"https://xt-ml.github.io/shadow-claw/#organization","name":"xt-ml","url":"https://github.com/xt-ml","sameAs":["https://github.com/xt-ml"]}]}</script><meta name="theme-color" content="#fff" /><meta http-equiv="origin-trial" content="A88uTgESYWcVgREKVReKB6jom41uP8TzW6ei3jNdidf+Xl6ONqKRjKNBphsxhrZmdkakLK3oXDgkGq53rtZYSAMAAAB2eyJvcmlnaW4iOiJodHRwczovL3h0LW1sLmdpdGh1Yi5pbzo0NDMiLCJmZWF0dXJlIjoiV2ViTUNQIiwiZXhwaXJ5IjoxNzk0ODczNjAwLCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ=="><base href="/shadow-claw/"><script id="shadow-claw-site-config" type="application/json">{"$schema":"https:\u002f\u002fjson-schema.org\u002fdraft\u002f2020-12\u002fschema","site":{"title":"ShadowClaw","description":"ShadowClaw - Browser-native AI agent.","themeColor":"#fff"},"branding":{"faviconPath":"assets\u002ficons\u002ffavicon.ico","appleTouchIconPath":"assets\u002ficons\u002f180.png","notFoundPath":"404.html"},"pwa":{"manifestPath":"manifest.json","themeColor":"#000000"},"sitemapPath":"sitemap.xml","assets":["assets"],"sidebar":{"pagesHidden":false,"chatHidden":false,"tasksHidden":false,"filesHidden":false,"defaultPage":"pages"},"settings":{"defaultToolsProfile":"__builtin_default"}}</script>
2
2
  <script>var ShadowClawThemeInit=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(){if(globalThis.trustedTypes)return;let e=new Map;globalThis.trustedTypes={createPolicy:(t,n)=>{if(e.has(t))throw TypeError(`Policy with name "${t}" already exists.`);let r={createHTML:n.createHTML?e=>n.createHTML(e):void 0,createScriptURL:n.createScriptURL?e=>n.createScriptURL(e):void 0,createScript:n.createScript?e=>n.createScript(e):void 0};return e.set(t,r),r},getPolicy:t=>e.get(t)??null,isHTML:e=>typeof e==`string`||e instanceof Object&&`toString`in e,isScriptURL:e=>typeof e==`string`||e instanceof Object&&`toString`in e,isScript:e=>typeof e==`string`||e instanceof Object&&`toString`in e}}let n=`__shadowClawDefaultTrustedTypesPolicyState`,r=`default`,i=`shadowclaw-sandbox`;function a(){let e=globalThis;return e[n]||(e[n]={initialized:!1,policy:null}),e[n]}function o(){let e=a();if(e.initialized)return;let t=Reflect.get(globalThis,`trustedTypes`);if(!(!t||typeof t.createPolicy!=`function`)){e.initialized=!0;try{if(typeof t.getPolicy==`function`){let n=t.getPolicy(r);if(n){e.policy=n;return}}let n=!1;if(typeof t.getPolicyNames==`function`){let e=t.getPolicyNames();Array.isArray(e)&&e.includes(r)&&(n=!0)}if(n){if(typeof t.getPolicy==`function`){let n=t.getPolicy(i);if(n){e.policy=n;return}}e.policy=t.createPolicy(i,{createHTML:e=>e,createScriptURL:e=>e});return}e.policy=t.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch{typeof t.getPolicy==`function`&&(e.policy=t.getPolicy(r)??t.getPolicy(i)??null)}}}new class{loading=!1;models=new Map;getModelInfo(e){return this.models.get(e.toLowerCase())??null}registerModelInfo(e,t){this.models.set(e.toLowerCase(),t)}clear(){this.models.clear()}async fetchModelInfo(e,t,n){if(e.modelsUrl){this.loading=!0;try{let r=new Headers;if(r.set(`Content-Type`,`application/json`),e.headers)for(let[t,n]of Object.entries(e.headers))r.set(t,n);if(n)for(let[e,t]of Object.entries(n))r.set(e,t);if(t&&e.apiKeyHeader){let n=e.apiKeyHeaderFormat;r.set(e.apiKeyHeader,n?n.replace(`{key}`,t):t)}let i=await fetch(e.modelsUrl,{headers:r});if(!i.ok)throw Error(`HTTP ${i.status} ${i.statusText}`);let a=await i.json(),o=Array.isArray(a.data)?a.data:Array.isArray(a.models)?a.models:[];for(let e of o){if(!e.id)continue;let t=e.context_length??e.context_window??0,n=e.max_completion_tokens||e.per_request_limits?.completion_tokens||e.top_provider?.max_completion_tokens||null,r=typeof e.supports_tools==`boolean`?e.supports_tools:typeof e.supportsTools==`boolean`?e.supportsTools:void 0,i=this.extractModalities(e,`input`),a=this.extractModalities(e,`output`),o=this.modalitiesInclude(i,`image`,`vision`),s=this.modalitiesInclude(i,`audio`,`voice`,`hearing`),c=this.modalitiesInclude(i,`video`),l=e.id===`openrouter/free`||this.supportedParametersInclude(e,`image`,`vision`,`audio`,`tool`,`structured`),u=this.extractReasoning(e);!t&&Array.isArray(e.providers)&&e.providers.length>0&&(t=e.providers[0]?.context_length||0),this.registerModelInfo(e.id,{contextWindow:t,maxOutput:n,...r!==void 0&&{supportsTools:r},...i.length>0&&{inputModalities:i},...a.length>0&&{outputModalities:a},...o!==void 0&&{supportsImageInput:o},...s!==void 0&&{supportsAudioInput:s},...c!==void 0&&{supportsVideoInput:c},...u&&{reasoning:u},...l&&{routesByRequestFeatures:l}})}o.length>0&&console.log(`[ModelRegistry] Registered ${o.length} models for provider "${e.id}"`)}catch(t){console.error(`[ModelRegistry] Error fetching models for ${e.id}:`,t)}finally{this.loading=!1}}}extractModalities(e,t){let n=t===`input`?e.input_modalities||e.inputModalities:e.output_modalities||e.outputModalities,r=e.architecture||{},i=t===`input`?r.input_modalities||r.inputModalities:r.output_modalities||r.outputModalities,a=typeof r.modality==`string`?r.modality:``,o=this.parseArchitectureModality(a,t),s=[...this.normalizeStringArray(n),...this.normalizeStringArray(i),...o];return Array.from(new Set(s))}extractReasoning(e){let t=e?.reasoning;if(!t||typeof t!=`object`)return;let n=this.normalizeStringArray(t.supported_efforts),r=typeof t.default_effort==`string`?t.default_effort.toLowerCase():void 0,i=typeof t.default_enabled==`boolean`?t.default_enabled:void 0,a=typeof t.supports_max_tokens==`boolean`?t.supports_max_tokens:void 0,o=typeof t.mandatory==`boolean`?t.mandatory:void 0;if(!(n.length===0&&r===void 0&&i===void 0&&a===void 0&&o===void 0))return{...n.length>0&&{supportedEfforts:n},...r!==void 0&&{defaultEffort:r},...i!==void 0&&{defaultEnabled:i},...a!==void 0&&{supportsMaxTokens:a},...o!==void 0&&{mandatory:o}}}modalitiesInclude(e,...t){if(e.length!==0)return t.some(t=>e.some(e=>e.includes(t)))}normalizeStringArray(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`).map(e=>e.toLowerCase()):[]}parseArchitectureModality(e,t){if(!e)return[];let[n=``,r=``]=e.toLowerCase().split(`->`).map(e=>e.trim());return(t===`input`?n:r).split(/[+,/\s]+/).map(e=>e.trim()).filter(Boolean)}supportedParametersInclude(e,...t){let n=this.normalizeStringArray(e.supported_parameters||e.supportedParameters);return t.some(e=>n.some(t=>t.includes(e)))}};function s(e){return e.replace(/^\/+|\/+$/g,``)}function c(e){let t=s(e);return t.endsWith(`/index.html`)?t=t.slice(0,-11):t.endsWith(`index.html`)&&(t=t.slice(0,-10)),s(t)}function l(e){let t=s(e),n=t.split(`/`).filter(Boolean);if(n.length===0)return null;let r=n[0].toLowerCase();if(r===`pages`){if(n.length>=3){let e=n[1];return{page:`pages`,groupId:e===`main`?`br:main`:e.startsWith(`br-`)?`br:${e.slice(3)}`:e,path:n.slice(2).join(`/`)}}return{page:`pages`,groupId:`br:main`,path:n.slice(1).join(`/`)}}if(r===`files`){let e=n[1]||`main`;return{page:`files`,groupId:e===`main`?`br:main`:e.startsWith(`br-`)?`br:${e.slice(3)}`:e,path:n.slice(2).join(`/`)||void 0}}if(r===`chat`){let e=n[1]||`main`;return{page:`chat`,groupId:e===`main`?`br:main`:e.startsWith(`br-`)?`br:${e.slice(3)}`:e}}if(r===`tasks`){let e=n[1]||`main`;return{page:`tasks`,groupId:e===`main`?`br:main`:e.startsWith(`br-`)?`br:${e.slice(3)}`:e}}return r===`main`?{page:`pages`,groupId:`br:main`,path:n.slice(1).join(`/`)}:{page:`pages`,groupId:`br:main`,path:t}}function u(){if(typeof document<`u`){let e=document.getElementById(`shadow-claw-static-routing`);if(e&&e.textContent)try{let t=JSON.parse(e.textContent);if(t&&typeof t==`object`&&t.routes)return t}catch(e){console.warn(`Failed to parse embedded static routing manifest:`,e)}}return null}function d(e,t){let n=t||u();if(!n||!n.routes)return null;let r=c(e);if(!r)return null;for(let[e,t]of Object.entries(n.routes))if(!(!t||!t.prettyPath)&&c(t.prettyPath)===r)return l(e);return null}let f=null,ee=new Set([`chat`,`files`,`tasks`,`pages`,`settings`,`tools`,`channels`]);function te(){f=null}typeof globalThis<`u`&&(globalThis.__applyBasePathCacheReset=te);function p(){let e=typeof window<`u`?window:typeof self<`u`?self:globalThis;if(e&&e.__SHADOWCLAW_DEPLOY_ID__){let t=String(e.__SHADOWCLAW_DEPLOY_ID__).trim();if(t)return t.replace(/[^a-zA-Z0-9_-]/g,`-`)}let t=globalThis.process;if(t&&t.env&&t.env.SHADOWCLAW_DEPLOY_ID){let e=String(t.env.SHADOWCLAW_DEPLOY_ID).trim();if(e)return e.replace(/[^a-zA-Z0-9_-]/g,`-`)}let n=m();return!n||n===`/`?``:n.replace(/^\/+|\/+$/g,``).replace(/[^a-zA-Z0-9_-]/g,`-`)}function m(){if(f!==null)return f;if((typeof window>`u`||globalThis.WorkerGlobalScope!==void 0&&typeof self<`u`&&self instanceof globalThis.WorkerGlobalScope)&&typeof self<`u`&&self.location){let e=(self.location.pathname||`/`).split(`/`).filter(Boolean);if(e.length<=1)return f=`/`,f;let t=e[0].toLowerCase(),n=t===`service-worker.js`||t===`sw.js`||t===`manifest.json`||t===`sitemap.xml`||t===`favicon.ico`||t===`index.html`||t===`service-worker`||t===`assets`||t===`dist`||t===`public`;if(n&&e.length===2)return f=`/`,f;if(!n)return f=`/`+e[0]+`/`,f}let e=typeof window<`u`&&window.location?window.location:typeof self<`u`&&self.location?self.location:null;if(!e)return`/`;if(typeof document<`u`&&typeof document.querySelector==`function`){let t=document.querySelector(`base[href]`);if(t){let n=t.getAttribute(`href`);if(n)try{let t=new URL(n,e.origin).pathname;return t.endsWith(`/`)||(t+=`/`),f=t,f}catch{}}}let t=e.pathname||`/`;if(t===`/`)return f=`/`,f;let n=t.split(`/`).filter(Boolean),r=n.findIndex(e=>ee.has(e.toLowerCase()));return r>=0?r===0?(f=`/`,f):(f=`/`+n.slice(0,r).join(`/`)+`/`,f):d(t)?(f=`/`,f):n.length===1&&!t.includes(`.`)?(f=`/`+n[0]+`/`,f):(f=`/`,f)}function ne(e){let t=e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return RegExp(`(^|\\s)@${t}\\b`,`i`)}ne(`ShadowClaw`);function re(){let e=p();return e?`shadowclaw-${e}`:`shadowclaw`}re();let h={github:{providerId:`github`,name:`GitHub`,aliases:[`github`,`api.github.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`,`git_remote`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`token `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://github.com/login/oauth/authorize`,tokenUrl:`https://github.com/login/oauth/access_token`,defaultScopes:[`repo`,`read:user`],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},gitlab:{providerId:`gitlab`,name:`GitLab`,aliases:[`gitlab`,`gitlab.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`,`git_remote`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`PRIVATE-TOKEN`,headerPrefix:``},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},tokenAuthByServiceType:{git_remote:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}}},oauth:{authorizeUrl:`https://gitlab.com/oauth/authorize`,tokenUrl:`https://gitlab.com/oauth/token`,defaultScopes:[`read_api`,`read_user`],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},figma:{providerId:`figma`,name:`Figma`,aliases:[`figma`,`api.figma.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`X-Figma-Token`,headerPrefix:``},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://www.figma.com/oauth`,tokenUrl:`https://api.figma.com/v1/oauth/token`,refreshUrl:`https://api.figma.com/v1/oauth/refresh`,defaultScopes:[],usePkce:!0,clientAuthMethod:`basic_header`,scopeSeparator:`space`}},notion:{providerId:`notion`,name:`Notion`,aliases:[`notion`,`api.notion.com`,`notion.so`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://api.notion.com/v1/oauth/authorize`,tokenUrl:`https://api.notion.com/v1/oauth/token`,defaultScopes:[],usePkce:!0,clientAuthMethod:`basic_header`,scopeSeparator:`space`}},microsoft_graph:{providerId:`microsoft_graph`,name:`Microsoft Graph`,aliases:[`microsoft`,`graph.microsoft.com`,`microsoft_graph`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://login.microsoftonline.com/common/oauth2/v2.0/authorize`,tokenUrl:`https://login.microsoftonline.com/common/oauth2/v2.0/token`,defaultScopes:[`openid`,`profile`,`offline_access`,`User.Read`],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},yahoo_mail:{providerId:`yahoo_mail`,name:`Yahoo Mail`,aliases:[`yahoo`,`yahoo_mail`,`mail.yahoo.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://api.login.yahoo.com/oauth2/request_auth`,tokenUrl:`https://api.login.yahoo.com/oauth2/get_token`,defaultScopes:[`mail-r`,`mail-w`],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},google:{providerId:`google`,name:`Google`,aliases:[`google`,`googleapis.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://accounts.google.com/o/oauth2/v2/auth`,tokenUrl:`https://oauth2.googleapis.com/token`,defaultScopes:[`openid`,`profile`,`email`],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},atlassian:{providerId:`atlassian`,name:`Atlassian`,aliases:[`atlassian`,`api.atlassian.com`],modes:[`oauth`,`token`,`basic`],defaultMode:`basic`,serviceTypes:[`http_api`,`git_remote`],authTypes:[`token`,`oauth`,`basic_userpass`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `},basic:{headerName:`Authorization`,headerPrefix:`Basic `}},oauth:{authorizeUrl:`https://auth.atlassian.com/authorize`,tokenUrl:`https://auth.atlassian.com/oauth/token`,defaultScopes:[],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},slack:{providerId:`slack`,name:`Slack`,aliases:[`slack`,`slack.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://slack.com/oauth/v2/authorize`,tokenUrl:`https://slack.com/api/oauth.v2.access`,defaultScopes:[],usePkce:!1,clientAuthMethod:`request_body`,scopeSeparator:`comma`}},linear:{providerId:`linear`,name:`Linear`,aliases:[`linear`,`api.linear.app`,`linear.app`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`],authTypes:[`token`,`oauth`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://linear.app/oauth/authorize`,tokenUrl:`https://api.linear.app/oauth/token`,defaultScopes:[],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}},azure_devops:{providerId:`azure_devops`,name:`Azure DevOps`,aliases:[`azure_devops`,`dev.azure.com`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`http_api`,`git_remote`],authTypes:[`token`,`oauth`,`basic_userpass`,`ssh_key`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},tokenAuthByServiceType:{git_remote:{token:{headerName:`Authorization`,headerPrefix:`Basic `},oauth:{headerName:`Authorization`,headerPrefix:`Basic `}}},oauth:{authorizeUrl:`https://app.vssps.visualstudio.com/oauth2/authorize`,tokenUrl:`https://app.vssps.visualstudio.com/oauth2/token`,defaultScopes:[`vso.code`,`vso.profile`,`offline_access`],usePkce:!1,clientAuthMethod:`request_body`,scopeSeparator:`space`}},custom_mcp:{providerId:`custom_mcp`,name:`Custom MCP`,aliases:[`custom_mcp`],modes:[`oauth`,`token`],defaultMode:`oauth`,serviceTypes:[`mcp_remote`,`webmcp_local`],authTypes:[`none`,`token`,`oauth`,`custom_header`,`ssh_key`],tokenAuth:{token:{headerName:`Authorization`,headerPrefix:`Bearer `},oauth:{headerName:`Authorization`,headerPrefix:`Bearer `}},oauth:{authorizeUrl:`https://example.com/oauth/authorize`,tokenUrl:`https://example.com/oauth/token`,defaultScopes:[],usePkce:!0,clientAuthMethod:`request_body`,scopeSeparator:`space`}}};Object.fromEntries(Object.values(h).filter(e=>!!e.oauth).map(e=>{let t=e.oauth;return[e.providerId,{id:e.providerId,name:e.name,authorizeUrl:t.authorizeUrl,tokenUrl:t.tokenUrl,redirectUri:t.redirectUri,defaultScopes:t.defaultScopes,usePkce:t.usePkce,clientAuthMethod:t.clientAuthMethod,scopeSeparator:t.scopeSeparator}]})),Object.fromEntries(Object.values(h).map(e=>[e.providerId,{providerId:e.providerId,modes:e.modes,defaultMode:e.defaultMode}])),new Promise(e=>{});
3
3
  /*! @license DOMPurify 3.4.13 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.13/LICENSE */
4
4
  function g(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function ie(e){if(Array.isArray(e))return e}function _(e,t){var n=e==null?null:typeof Symbol<`u`&&e[Symbol.iterator]||e[`@@iterator`];if(n!=null){var r,i,a,o,s=[],c=!0,l=!1;try{if(a=(n=n.call(e)).next,t!==0)for(;!(c=(r=a.call(n)).done)&&(s.push(r.value),s.length!==t);c=!0);}catch(e){l=!0,i=e}finally{try{if(!c&&n.return!=null&&(o=n.return(),Object(o)!==o))return}finally{if(l)throw i}}return s}}function v(){throw TypeError(`Invalid attempt to destructure non-iterable instance.
@@ -0,0 +1,334 @@
1
+ # Publishing to GitHub Pages
2
+
3
+ > How to drive automated static-site publishing pipelines with ShadowClaw and GitHub Actions.
4
+
5
+ ShadowClaw can drive a fully automated static-site publishing pipeline directly from the browser, with the only server-side requirement being a GitHub Actions workflow that runs the Node.js build.
6
+
7
+ ---
8
+
9
+ ## Deployment Strategies
10
+
11
+ ### Strategy A — Full fork
12
+
13
+ Clone the entire ShadowClaw repository, add your pages to `pages/main/`, push to a new repo you own, and let CI build and deploy it.
14
+
15
+ ### Strategy B — Pages-only repo (Git clone & inject in CI)
16
+
17
+ Your repo contains your content, an optional root-level `shadow-claw.config.json` (or `site-config.json`), and a GitHub Actions workflow. Content lives under `pages/`; the workflow checks out ShadowClaw as a build toolchain in CI and builds into `dist/public`.
18
+
19
+ ### Strategy C — NPM Package & CLI (`npx shadow-claw`)
20
+
21
+ Run or deploy using the `shadow-claw` npm package directly:
22
+
23
+ - Locally: `npx shadow-claw dev` (or `shadow-claw run`) to preview with dev server.
24
+ - In CI: `npx shadow-claw build --prod` builds `dist/public` without needing to clone ShadowClaw or copy files.
25
+ - In `package.json`: add `"shadow-claw": "^1.28.0"` as a devDependency and run `npm run build`.
26
+
27
+ **Strategy B and Strategy C are both fully supported for content-only repositories and knowledge hubs.**
28
+
29
+ ---
30
+
31
+ ## Strategy B — Step-by-step
32
+
33
+ ### 1. Create a new GitHub repository
34
+
35
+ Create an empty repo at `github.com/<you>/<your-site>`. Enable **Settings → Pages → Source: GitHub Actions**.
36
+
37
+ ### 2. Add the workflow
38
+
39
+ Create `.github/workflows/deploy-pages.yml`:
40
+
41
+ ```yaml
42
+ name: Build and Deploy via ShadowClaw
43
+
44
+ on:
45
+ push:
46
+ branches: [main]
47
+ workflow_dispatch:
48
+ inputs:
49
+ shadowclaw_version:
50
+ description: "ShadowClaw npm version (e.g. latest, 1.28.0)"
51
+ required: false
52
+ default: "latest"
53
+
54
+ permissions:
55
+ contents: read
56
+ pages: write
57
+ id-token: write
58
+
59
+ concurrency:
60
+ group: pages
61
+ cancel-in-progress: true
62
+
63
+ jobs:
64
+ build-and-deploy:
65
+ environment:
66
+ name: github-pages
67
+ url: ${{ steps.deployment.outputs.page_url }}
68
+ runs-on: ubuntu-latest
69
+ steps:
70
+ - name: Checkout repository
71
+ uses: actions/checkout@v6
72
+
73
+ - name: Setup Node.js
74
+ uses: actions/setup-node@v6
75
+ with:
76
+ node-version: "24"
77
+
78
+ - name: Build static site via ShadowClaw CLI
79
+ env:
80
+ PAGES_ORIGIN: "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"
81
+ PAGES_BASE_PATH: "/${{ github.event.repository.name }}/"
82
+ run: |
83
+ VERSION="${{ github.event.inputs.shadowclaw_version || 'latest' }}"
84
+ npx --yes "shadow-claw@$VERSION" build --prod
85
+
86
+ - name: Upload Pages artifact
87
+ uses: actions/upload-pages-artifact@v3
88
+ with:
89
+ path: dist/public
90
+
91
+ - name: Deploy to GitHub Pages
92
+ id: deployment
93
+ uses: actions/deploy-pages@v4
94
+ ```
95
+
96
+ ### Version Pinning
97
+
98
+ ShadowClaw builds via `npx --yes shadow-claw@latest build --prod`. To pin to a specific npm release, specify the version in `.github/workflows/deploy-pages.yml` or supply `shadowclaw_version` when triggering the GitHub Actions workflow manually.
99
+
100
+ For a **custom apex domain** (e.g. `example.com`), override:
101
+
102
+ ```yaml
103
+ PAGES_ORIGIN: "https://example.com/"
104
+ PAGES_BASE_PATH: "/"
105
+ ```
106
+
107
+ ### 3. Add your content
108
+
109
+ ```text
110
+ pages/
111
+ main/
112
+ index.html ← home page
113
+ ~/content/
114
+ about.md ← any other pages
115
+ routes.json ← pretty-path config (optional)
116
+
117
+ shadow-claw.config.json ← branding, default tool settings, and build configuration (optional)
118
+ ```
119
+
120
+ Site configurations can also specify tool defaults under `settings`:
121
+
122
+ ```json
123
+ {
124
+ "settings": {
125
+ "defaultToolsProfile": "__builtin_default",
126
+ "enabledTools": ["javascript", "read_file", "write_file"]
127
+ }
128
+ }
129
+ ```
130
+
131
+ The `pages/` directory is optional. If it is absent, the build succeeds and ShadowClaw publishes its built-in `index.html` and `MEMORY.md` Pages content. Pretty paths are skipped unless a `routes.json` file is present.
132
+
133
+ Minimal `pages/routes.json`:
134
+
135
+ ```json
136
+ {
137
+ "routes": {
138
+ "/pages/main/index.html": { "prettyPath": "/main" },
139
+ "/pages/main/~/content/about.md": { "prettyPath": "/main/about" },
140
+ "/pages/main/MEMORY.md": { "prettyPath": "/main/memory" }
141
+ }
142
+ }
143
+ ```
144
+
145
+ > **Reserved path prefixes** — the following first-path-segments are owned by ShadowClaw's own router and **must not** be used as pretty path prefixes:
146
+ > `/`, `/chat`, `/files`, `/tasks`, `/pages`, `/settings`, `/tools`, `/channels`.
147
+ > `/` (root) is also reserved as the default pinned page and is unreachable as a pretty path. Use a safe namespace like `/main/`, `/articles/`, `/docs/`, or any custom prefix that doesn't collide with the list above.
148
+
149
+ #### Default Pinned Page (`/`)
150
+
151
+ When visitors navigate to the root URL (`/`) of your published site, ShadowClaw displays the **default pinned page**.
152
+
153
+ **How ShadowClaw selects the default page for `/`:**
154
+
155
+ 1. Both the static site prerenderer (`prerender-dsd-shell`) and runtime page store (`orchestratorStore`) gather all page files in `pages/main/`. If `pages/main/` is absent, both use the built-in default `index.html` and `MEMORY.md` pages.
156
+ 2. `MEMORY.md` is always moved to the bottom of the list.
157
+ 3. All remaining pages are sorted by `pages.sortOrder` from `shadow-claw.config.json` (or `site-config.json`) (`"desc"` by default, natural numeric, or `"asc"`).
158
+ 4. The first file in this sorted list (`pages[0]`) becomes the default page rendered at `/`.
159
+
160
+ **How to ensure your intended home page is rendered at `/`:**
161
+ Name your landing page file so it sorts first in reverse-alphabetical order relative to other files in `pages/main/`:
162
+
163
+ - `index.md` or `index.html` will sort ahead of `about.md`, `contact.md`, or `faq.md`.
164
+ - If you have files starting with letters after `i` (e.g. `welcome.md`), `welcome.md` will sort ahead of `index.md`. Ensure your preferred landing page filename sorts highest in reverse-alphabetical order.
165
+
166
+ ### 4. Push — CI does the rest
167
+
168
+ Every push to `main` triggers a build and deploys to `https://<you>.github.io/<your-site>/`.
169
+
170
+ ---
171
+
172
+ ## In-browser automation (no LLM, no server)
173
+
174
+ ShadowClaw can publish to your content repo entirely from the browser using a `type: "tools"` task chain. The chain is deterministic — it makes **zero LLM calls**.
175
+
176
+ ### Prerequisites
177
+
178
+ - Clone your content repo into the workspace: ask the agent to `git_clone https://github.com/<you>/<your-site>`, or do it manually.
179
+ - Configure a GitHub Personal Access Token in **Settings → Git → PAT** (needs `repo` scope for private repos, `public_repo` for public ones).
180
+
181
+ ### Minimal publish chain
182
+
183
+ ```json
184
+ {
185
+ "type": "tools",
186
+ "tools": [
187
+ {
188
+ "name": "write_file",
189
+ "input": {
190
+ "path": "repos/your-site/pages/main/post.md",
191
+ "content": "---\ntitle: My Post\n---\n\n# Hello World\n\nContent here."
192
+ }
193
+ },
194
+ {
195
+ "name": "git_add",
196
+ "input": {
197
+ "repo": "your-site",
198
+ "files": ["pages/main/post.md"]
199
+ }
200
+ },
201
+ {
202
+ "name": "git_commit",
203
+ "input": {
204
+ "repo": "your-site",
205
+ "message": "publish: new post"
206
+ }
207
+ },
208
+ {
209
+ "name": "git_push",
210
+ "input": {
211
+ "repo": "your-site"
212
+ }
213
+ }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ ### Fetch-transform-publish chain (fully automated data pipeline)
219
+
220
+ This chain fetches an external data source, transforms it to markdown via the JavaScript sandbox, writes it as a page, and pushes — no human in the loop:
221
+
222
+ ```json
223
+ {
224
+ "type": "tools",
225
+ "tools": [
226
+ {
227
+ "name": "fetch_url",
228
+ "input": {
229
+ "url": "https://api.github.com/repos/xt-ml/shadow-claw/releases/latest"
230
+ }
231
+ },
232
+ {
233
+ "name": "javascript",
234
+ "input": {
235
+ "code": "const match = $PIPE_DATA.match(/--- BEGIN EXTERNAL CONTENT[\\s\\S]*?---\\n([\\s\\S]*?)\\n--- END EXTERNAL CONTENT ---/); const raw = match ? match[1] : $PIPE_DATA; const rel = JSON.parse(raw); const date = rel.published_at?.slice(0, 10) ?? 'unknown'; return `---\\ntitle: ShadowClaw ${rel.tag_name}\\ndate: ${date}\\n---\\n\\n# ${rel.name}\\n\\nPublished: ${date}\\n\\n${rel.body ?? ''}`;",
236
+ "data": { "$pipe": "prev" }
237
+ }
238
+ },
239
+ {
240
+ "name": "write_file",
241
+ "input": {
242
+ "path": "repos/your-site/pages/main/release-notes.md",
243
+ "content": { "$pipe": "prev" }
244
+ }
245
+ },
246
+ {
247
+ "name": "git_add",
248
+ "input": {
249
+ "repo": "your-site",
250
+ "files": ["pages/main/release-notes.md"]
251
+ }
252
+ },
253
+ {
254
+ "name": "git_commit",
255
+ "input": {
256
+ "repo": "your-site",
257
+ "message": "shadow-claw[bot]: sync release notes"
258
+ }
259
+ },
260
+ {
261
+ "name": "git_push",
262
+ "input": {
263
+ "repo": "your-site"
264
+ }
265
+ }
266
+ ]
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Strategy C — NPM Package & CLI Workflow
273
+
274
+ If you prefer building directly with `npx shadow-claw` without cloning the ShadowClaw repository in CI, use this minimal workflow:
275
+
276
+ ```yaml
277
+ name: Build and Deploy via ShadowClaw CLI
278
+
279
+ on:
280
+ push:
281
+ branches: [main]
282
+ workflow_dispatch:
283
+
284
+ permissions:
285
+ contents: read
286
+ pages: write
287
+ id-token: write
288
+
289
+ concurrency:
290
+ group: pages
291
+ cancel-in-progress: true
292
+
293
+ jobs:
294
+ build:
295
+ runs-on: ubuntu-latest
296
+ steps:
297
+ - name: Checkout content repo
298
+ uses: actions/checkout@v4
299
+
300
+ - uses: actions/setup-node@v4
301
+ with:
302
+ node-version: "24"
303
+
304
+ - name: Build production bundle
305
+ env:
306
+ NODE_ENV: production
307
+ PAGES_ORIGIN: "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"
308
+ PAGES_BASE_PATH: "/${{ github.event.repository.name }}/"
309
+ run: npx shadow-claw build --prod
310
+
311
+ - name: Upload Pages artifact
312
+ uses: actions/upload-pages-artifact@v3
313
+ with:
314
+ path: dist/public
315
+
316
+ deploy:
317
+ needs: build
318
+ runs-on: ubuntu-latest
319
+ environment:
320
+ name: github-pages
321
+ url: ${{ steps.deployment.outputs.page_url }}
322
+ steps:
323
+ - name: Deploy to GitHub Pages
324
+ id: deployment
325
+ uses: actions/deploy-pages@v4
326
+ ```
327
+
328
+ > **Tip:** Wrap this in a scheduled task (Tasks sidebar → New Task → type `tools`, set a cron interval) to run the pipeline automatically, e.g. every 24 hours. No LLM calls are made at execution time.
329
+
330
+ ---
331
+
332
+ ## Template repository
333
+
334
+ A ready-to-fork starter template is available in the [`shadow-claw-template`](https://github.com/xt-ml/shadow-claw-template) repository. It contains the workflow, sample `pages/main/index.html`, `pages/main/~/content/about.md`, root-level `shadow-claw.config.json`, and `pages/routes.json`.
@@ -123,6 +123,21 @@ npx shadow-claw dev --https --cert /path/to/cert.pem --key /path/to/key.pem
123
123
  npm start -- 8888 --https --cert /path/to/cert.pem --key /path/to/key.pem
124
124
  ```
125
125
 
126
+ #### Manual TLS Certificate Generation (OpenSSL)
127
+
128
+ To manually generate a self-signed certificate with Subject Alternative Name (SAN) extensions covering local hostnames and IP addresses:
129
+
130
+ ```bash
131
+ openssl req -x509 -newkey rsa:2048 -nodes -days 398 \
132
+ -keyout .cache/tls/key.pem \
133
+ -out .cache/tls/cert.pem \
134
+ -subj "/CN=book" \
135
+ -addext "subjectAltName=DNS:exampleHostname,IP:192.168.1.200,DNS:localhost,IP:127.0.0.1" \
136
+ -addext "extendedKeyUsage=serverAuth"
137
+ ```
138
+
139
+ Certificates placed in `.cache/tls/key.pem` and `.cache/tls/cert.pem` are automatically loaded when running with `--https` (via `SHADOWCLAW_SSL_DIR` / `config.sslDir`) without requiring explicit `--cert` or `--key` flags.
140
+
126
141
  ### Cache & Storage Configuration
127
142
 
128
143
  **Flags:** `--cache-dir`, `--tmp` / `--temp`, `-y` / `--yes`