shadow-claw 1.41.0 → 1.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/README.md +2 -2
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/{chunk-bQQkVCw-.js → chunk-63s8NnIS.js} +15 -16
- package/dist/cli/chunk-BATVgkFY.js +17 -0
- package/dist/cli/chunk-Cmdxoq9q.js +13 -0
- package/dist/cli/cli.js +1 -1
- package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/AGENTS.md +6 -1
- package/dist/public/README.md +2 -2
- package/dist/public/docs/README.md +1 -0
- package/dist/public/docs/example/article/index.html +1 -1
- package/dist/public/docs/guides/publishing-to-github-pages.md +46 -0
- package/dist/public/docs/publishing/index.html +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- package/dist/public/docs/subsystems/theming.md +242 -0
- package/dist/public/e2e/README.md +1 -0
- package/dist/public/index.html +1 -1
- package/dist/public/llms.txt +2 -2
- package/dist/public/main/index.html +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/service-worker.js +1 -1
- package/package.json +1 -1
- package/src/cli/build/build.ts +18 -11
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +6 -0
- package/src/cli/prerender/pretty-paths/prerender-pretty-paths.ts +22 -0
- package/src/cli/prerender/utils/insert-before-closing-head.test.ts +22 -1
- package/src/cli/prerender/utils/insert-before-closing-head.ts +19 -3
- package/src/cli/site-config/apply.ts +13 -14
- package/dist/cli/chunk-BvVUnHTq.js +0 -14
- package/dist/cli/chunk-DTES-dE6.js +0 -17
package/dist/public/README.md
CHANGED
|
@@ -116,7 +116,7 @@ npm install && npm run dev # Dev server → http://localhost:8888
|
|
|
116
116
|
- **Dual-Dispatch Sandboxing & Storage:** Client-side compute via sandboxed JavaScript and `just-bash` (or WebVM Alpine Linux) backed by Origin Private File System (OPFS) and IndexedDB; paired with host-native Node filesystem handles and SQLite database persistence on the server side.
|
|
117
117
|
- **PWA & Electron Desktop Parity:** Deployable as a progressive web app with Service Worker and Web Push, or as a native desktop application with full feature parity.
|
|
118
118
|
- **Modular Package Library Exports & Standardized Utilities:** Exposes modular ESM exports declared in `package.json` under `.`, `./cli`, `./cli/*`, `./components`, `./components/*`, `./utils`, and `./utils/*`, compiled via Rolldown with dynamic entry point discovery for reusable components and standardized cross-cutting utilities (`base64`, `coerce`, `delay`, `filename`, `isBinary`, `mime`, `normalizeStringList`, `normalizeWorkspacePath`, `parseBooleanConfig`, `parseStoredStringList`, `ulid`).
|
|
119
|
-
- **Static Publishing & Slotted Navigation:** Publish documentation portals and knowledge hubs with Declarative Shadow DOM (DSD) SSR, root and sub-route pretty paths, customizable slotted
|
|
119
|
+
- **Static Publishing, Theming & Slotted Navigation:** Publish documentation portals and knowledge hubs with Declarative Shadow DOM (DSD) SSR, root and sub-route pretty paths, custom stylesheets (`theme.stylesheet`), CSS custom property layout/shell tokens, customizable slotted navigation (`sidebar-nav`, `sidebar`, `sidebar-content`, `sidebar-footer`), and declarative sections. See [Theming Subsystem](docs/subsystems/theming.md).
|
|
120
120
|
- **Control Plane, CLI & Native MCP Server:** Backed by a Node.js service layer and CLI (`shadow-claw`) that provides background cron scheduling, remote backups, direct WebRTC DataChannel connectivity, and a Stateless MCP server featuring both native server management tools and dynamic browser tool relaying to external agent hosts (Claude Desktop, Cursor, Goose).
|
|
121
121
|
|
|
122
122
|
---
|
|
@@ -287,7 +287,7 @@ Each channel creates isolated conversations with their own message history and w
|
|
|
287
287
|
Comprehensive architectural specifications, subsystem deep-dives, step-by-step guides, and ADRs live in [`docs/`](docs/README.md):
|
|
288
288
|
|
|
289
289
|
- **[Architecture](docs/README.md#architecture):** [System Overview](docs/architecture/overview.md) · [Orchestrator & State Machine](docs/architecture/orchestrator.md) · [Worker Protocol](docs/architecture/worker-protocol.md) · [Storage System](docs/architecture/storage.md) · [Context Management](docs/architecture/context-management.md) · [Streaming](docs/architecture/streaming.md)
|
|
290
|
-
- **[Subsystems](docs/README.md#subsystems):** [Headless Agent & CLI](docs/subsystems/cli.md) · [Local Models & Hugging Face](docs/subsystems/local-models.md) · [Shell Emulator](docs/subsystems/shell.md) · [WebVM](docs/subsystems/vm.md) · [Git Integration](docs/subsystems/git.md) · [Channels](docs/subsystems/channels.md) · [Tools & Profiles](docs/subsystems/tools.md) · [Providers & Model Registry](docs/subsystems/providers.md) · [Notifications & Scheduling](docs/subsystems/notifications.md) · [Electron Desktop](docs/subsystems/electron.md) · [Reactive UI & Web Components](docs/subsystems/reactive-ui.md) · [Remote MCP](docs/subsystems/remote-mcp.md) · [Stateless MCP Server](docs/subsystems/mcp-server.md) · [WebMCP](docs/subsystems/webmcp.md) · [Crypto & Secrets](docs/subsystems/crypto.md) · [Control Plane](docs/subsystems/control-plane.md) · [Pages System](docs/subsystems/pages.md) · [Agent Skills](docs/subsystems/skills.md) · [Security Hardening](docs/subsystems/custom-element-security.md) · [File Backup](docs/subsystems/backup.md) · [Web Share Target](docs/subsystems/share-target.md) · [OpenAPI](docs/subsystems/openapi.md)
|
|
290
|
+
- **[Subsystems](docs/README.md#subsystems):** [Headless Agent & CLI](docs/subsystems/cli.md) · [Local Models & Hugging Face](docs/subsystems/local-models.md) · [Shell Emulator](docs/subsystems/shell.md) · [WebVM](docs/subsystems/vm.md) · [Git Integration](docs/subsystems/git.md) · [Channels](docs/subsystems/channels.md) · [Tools & Profiles](docs/subsystems/tools.md) · [Providers & Model Registry](docs/subsystems/providers.md) · [Notifications & Scheduling](docs/subsystems/notifications.md) · [Electron Desktop](docs/subsystems/electron.md) · [Reactive UI & Web Components](docs/subsystems/reactive-ui.md) · [Remote MCP](docs/subsystems/remote-mcp.md) · [Stateless MCP Server](docs/subsystems/mcp-server.md) · [WebMCP](docs/subsystems/webmcp.md) · [Crypto & Secrets](docs/subsystems/crypto.md) · [Control Plane](docs/subsystems/control-plane.md) · [Pages System](docs/subsystems/pages.md) · [Theming & Stylesheets](docs/subsystems/theming.md) · [Agent Skills](docs/subsystems/skills.md) · [Security Hardening](docs/subsystems/custom-element-security.md) · [File Backup](docs/subsystems/backup.md) · [Web Share Target](docs/subsystems/share-target.md) · [OpenAPI](docs/subsystems/openapi.md)
|
|
291
291
|
- **[Guides](docs/README.md#guides):** [Adding a Provider](docs/guides/adding-a-provider.md) · [Adding a Tool](docs/guides/adding-a-tool.md) · [Adding a Shell Command](docs/guides/adding-a-shell-command.md) · [Adding a UI Page](docs/guides/adding-a-page.md) · [Adding a Channel](docs/guides/adding-a-channel.md) · [Protocol-Agnostic Integrations](docs/guides/protocol-agnostic-integrations.md) · [Service Accounts & Credentials](docs/guides/adding-service-accounts.md) · [Configuring Messaging Channels](docs/guides/configuring-messaging-channels.md) · [Server Development Configuration](docs/guides/server-development-configuration.md) · [Publishing to GitHub Pages](docs/guides/publishing-to-github-pages.md)
|
|
292
292
|
- **[Decisions](docs/README.md#decisions):** ADRs on [Headless CLI Agent Participant](docs/decisions/headless-cli-agent-participant.md), [Bundled TypeScript Architecture](docs/decisions/bundled-typescript-architecture.md), [Native Web Components and Signals](docs/decisions/native-web-components-and-signals.md), [Worker-Isolated Agent Runtime](docs/decisions/worker-isolated-agent-runtime.md), [IndexedDB and OPFS Storage](docs/decisions/indexeddb-and-opfs-storage.md), and [Peer-to-Peer Protocol (A2A via AGUI)](docs/decisions/peer-protocol-a2a-agui.md)
|
|
293
293
|
- **[Agent Conventions](AGENTS.md):** Architectural guardrails and conventions for AI coding agents
|
|
@@ -45,6 +45,7 @@ Detailed docs for each major subsystem.
|
|
|
45
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
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
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
|
+
| [Theming & Stylesheets](subsystems/theming.md) | Custom site theming, `theme.stylesheet`, CSS custom properties, layout/shell visibility tokens, light DOM slotted navigation styling, and preview iframe theme sync |
|
|
48
49
|
| [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
50
|
| [CLI & Static Site Publishing](subsystems/cli.md) | `shadow-claw` CLI commands (`agent`, `build`, `dev`, `serve`, `server`, `init`, `clients`, `send`, `backup`, `tasks`, `mcp`, `skills:index`), headless agent participant, dual-root path resolution, and npm packaging |
|
|
50
51
|
| [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 |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="
|
|
1
|
+
<!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="b1cf3de96d967a8393a81ef9dd2c91a54ec79bec" /><meta name="version" content="1.41.1" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><meta name="description" content="ShadowClaw - multi-runtime AI assistant." /><link rel="canonical" href="https://xt-ml.github.io/shadow-claw/"><meta property="og:title" content="ShadowClaw"><meta property="og:description" content="Multi-runtime 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="Multi-runtime 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":"Multi-runtime 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 multi-runtime 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 - multi-runtime AI assistant.","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"},"agent":{"defaultProvider":"transformers_js_local","defaultModel":"onnx-community\u002fgemma-3-1b-it-ONNX-GQA"},"customElements":{"allowedElements":["block-garden","block-garden-option","block-garden-select"],"allowedDomains":["kherrick.github.io","cdn.jsdelivr.net"]},"security":{"connectSrc":["'self'","blob:","data:","https:\u002f\u002fkherrick.github.io","https:\u002f\u002fcdn.jsdelivr.net"]}}</script>
|
|
2
2
|
<script>var ShadowClawThemeInit=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);function n(){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 r=`__shadowClawDefaultTrustedTypesPolicyState`,i=`default`,a=`shadowclaw-sandbox`;function o(){let e=globalThis;return e[r]||(e[r]={initialized:!1,policy:null}),e[r]}function s(){let e=o();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(i);if(n){e.policy=n;return}}let n=!1;if(typeof t.getPolicyNames==`function`){let e=t.getPolicyNames();Array.isArray(e)&&e.includes(i)&&(n=!0)}if(n){if(typeof t.getPolicy==`function`){let n=t.getPolicy(a);if(n){e.policy=n;return}}e.policy=t.createPolicy(a,{createHTML:e=>e,createScriptURL:e=>e});return}e.policy=t.createPolicy(i,{createHTML:e=>e,createScriptURL:e=>e})}catch{typeof t.getPolicy==`function`&&(e.policy=t.getPolicy(i)??t.getPolicy(a)??null)}}}new class{loading=!1;models=new Map;getModelInfo(e){if(!e||typeof e!=`string`)return null;let t=e.toLowerCase().trim(),n=this.models.get(t);if(n)return n;if(t.includes(`/`)){let e=t.split(`/`).pop();if(e&&this.models.has(e))return this.models.get(e)}else for(let[e,n]of this.models.entries())if(e.endsWith(`/${t}`))return n;if(t.includes(`:`)){let e=t.split(`:`)[0],n=this.getModelInfo(e);if(n)return n}return 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),d=typeof e.supports_prompt_caching==`boolean`?e.supports_prompt_caching:typeof e.supportsPromptCaching==`boolean`?e.supportsPromptCaching:this.supportedParametersInclude(e,`cache_control`,`prompt_caching`)?!0:void 0;!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},...d!==void 0&&{supportsPromptCaching:d}})}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 c(e){return e.replace(/^\/+|\/+$/g,``)}function l(e){let t=c(e);return t.endsWith(`/index.html`)?t=t.slice(0,-11):t.endsWith(`index.html`)&&(t=t.slice(0,-10)),c(t)}function u(e){let t=c(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 d(){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 f(e,t){let n=t||d();if(!n||!n.routes)return null;let r=l(e);for(let[e,t]of Object.entries(n.routes))if(!(!t||!t.prettyPath)&&l(t.prettyPath)===r)return u(e);return null}let p=null,ee=new Set([`chat`,`files`,`tasks`,`pages`,`settings`,`tools`,`channels`]);function m(){p=null}typeof globalThis<`u`&&(globalThis.__applyBasePathCacheReset=m);function h(){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=te();return!n||n===`/`?``:n.replace(/^\/+|\/+$/g,``).replace(/[^a-zA-Z0-9_-]/g,`-`)}function te(){if(p!==null)return p;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 p=`/`,p;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 p=`/`,p;if(!n)return p=`/`+e[0]+`/`,p}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+=`/`),p=t,p}catch{}}}let t=e.pathname||`/`;if(t===`/`)return p=`/`,p;let n=t.split(`/`).filter(Boolean),r=n.findIndex(e=>ee.has(e.toLowerCase()));return r>=0?r===0?(p=`/`,p):(p=`/`+n.slice(0,r).join(`/`)+`/`,p):f(t)?(p=`/`,p):n.length===1&&!t.includes(`.`)?(p=`/`+n[0]+`/`,p):(p=`/`,p)}function ne(e){let t=e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return RegExp(`(^|\\s)@${t}\\b`,`i`)}ne(`ShadowClaw`);function g(){let e=h();return e?`shadowclaw-${e}`:`shadowclaw`}g();let _={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(_).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(_).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 v(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 y(e){if(Array.isArray(e))return e}function b(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 re(){throw TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
@@ -189,6 +189,52 @@ Customize the navigation sidebar either by providing a custom sidebar template o
|
|
|
189
189
|
```
|
|
190
190
|
Slotted navigation links (`.sidebar-link`) automatically synchronize `.active` and `aria-current="page"` on route navigation, and clicking a slotted link automatically closes the navigation drawer on mobile viewports.
|
|
191
191
|
|
|
192
|
+
#### Custom Theming, Stylesheets & Layout Tokens
|
|
193
|
+
|
|
194
|
+
Sites can supply custom styling and override shell visibility by declaring `theme.stylesheet` in `shadow-claw.config.json` (see [Theming Subsystem](../subsystems/theming.md)):
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"theme": {
|
|
199
|
+
"stylesheet": "pages/main/theme.css"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
In `pages/main/theme.css`, you can override CSS custom property tokens and style the light DOM slotted sidebar:
|
|
205
|
+
|
|
206
|
+
```css
|
|
207
|
+
:root {
|
|
208
|
+
/* Hide unwanted built-in shell elements */
|
|
209
|
+
--shadow-claw-sidebar-footer-display: none; /* Hides Settings footer */
|
|
210
|
+
--shadow-claw-header-main-toggle-display: none; /* Hides top drawer menu */
|
|
211
|
+
--shadow-claw-page-header-display: none; /* Hides page header bar */
|
|
212
|
+
--shadow-claw-pages-dropdown-container-display: none; /* Hides pages dropdown */
|
|
213
|
+
--shadow-claw-article-header-margin-bottom: 1.5rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Prevent outer window dual scrollbars */
|
|
217
|
+
html:has(shadow-claw),
|
|
218
|
+
body:has(shadow-claw) {
|
|
219
|
+
height: 100%;
|
|
220
|
+
height: 100dvh;
|
|
221
|
+
overflow: hidden;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* Make custom slotted sidebar scroll vertically within the viewport */
|
|
225
|
+
.sidebar-custom-sections {
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-direction: column;
|
|
228
|
+
gap: 0.5rem;
|
|
229
|
+
max-height: calc(100vh - 4rem);
|
|
230
|
+
max-height: calc(100dvh - 4rem);
|
|
231
|
+
overflow-y: auto;
|
|
232
|
+
padding: 0.5rem;
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The build pipeline (`npx shadow-claw build --prod`) copies the stylesheet into `dist/public`, injects `<link rel="stylesheet">` before `</head>` in all pre-rendered HTML pages, and automatically mirrors the stylesheet and CSS tokens into sandboxed preview iframes.
|
|
237
|
+
|
|
192
238
|
#### Default Pinned Page (`/`)
|
|
193
239
|
|
|
194
240
|
When visitors navigate to the root URL (`/`) of your published site, ShadowClaw displays the **default pinned page**.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="
|
|
1
|
+
<!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="b1cf3de96d967a8393a81ef9dd2c91a54ec79bec" /><meta name="version" content="1.41.1" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><meta name="description" content="ShadowClaw - multi-runtime AI assistant." /><link rel="canonical" href="https://xt-ml.github.io/shadow-claw/"><meta property="og:title" content="ShadowClaw"><meta property="og:description" content="Multi-runtime 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="Multi-runtime 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":"Multi-runtime 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 multi-runtime 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 - multi-runtime AI assistant.","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"},"agent":{"defaultProvider":"transformers_js_local","defaultModel":"onnx-community\u002fgemma-3-1b-it-ONNX-GQA"},"customElements":{"allowedElements":["block-garden","block-garden-option","block-garden-select"],"allowedDomains":["kherrick.github.io","cdn.jsdelivr.net"]},"security":{"connectSrc":["'self'","blob:","data:","https:\u002f\u002fkherrick.github.io","https:\u002f\u002fcdn.jsdelivr.net"]}}</script>
|
|
2
2
|
<script>var ShadowClawThemeInit=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);function n(){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 r=`__shadowClawDefaultTrustedTypesPolicyState`,i=`default`,a=`shadowclaw-sandbox`;function o(){let e=globalThis;return e[r]||(e[r]={initialized:!1,policy:null}),e[r]}function s(){let e=o();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(i);if(n){e.policy=n;return}}let n=!1;if(typeof t.getPolicyNames==`function`){let e=t.getPolicyNames();Array.isArray(e)&&e.includes(i)&&(n=!0)}if(n){if(typeof t.getPolicy==`function`){let n=t.getPolicy(a);if(n){e.policy=n;return}}e.policy=t.createPolicy(a,{createHTML:e=>e,createScriptURL:e=>e});return}e.policy=t.createPolicy(i,{createHTML:e=>e,createScriptURL:e=>e})}catch{typeof t.getPolicy==`function`&&(e.policy=t.getPolicy(i)??t.getPolicy(a)??null)}}}new class{loading=!1;models=new Map;getModelInfo(e){if(!e||typeof e!=`string`)return null;let t=e.toLowerCase().trim(),n=this.models.get(t);if(n)return n;if(t.includes(`/`)){let e=t.split(`/`).pop();if(e&&this.models.has(e))return this.models.get(e)}else for(let[e,n]of this.models.entries())if(e.endsWith(`/${t}`))return n;if(t.includes(`:`)){let e=t.split(`:`)[0],n=this.getModelInfo(e);if(n)return n}return 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),d=typeof e.supports_prompt_caching==`boolean`?e.supports_prompt_caching:typeof e.supportsPromptCaching==`boolean`?e.supportsPromptCaching:this.supportedParametersInclude(e,`cache_control`,`prompt_caching`)?!0:void 0;!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},...d!==void 0&&{supportsPromptCaching:d}})}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 c(e){return e.replace(/^\/+|\/+$/g,``)}function l(e){let t=c(e);return t.endsWith(`/index.html`)?t=t.slice(0,-11):t.endsWith(`index.html`)&&(t=t.slice(0,-10)),c(t)}function u(e){let t=c(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 d(){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 f(e,t){let n=t||d();if(!n||!n.routes)return null;let r=l(e);for(let[e,t]of Object.entries(n.routes))if(!(!t||!t.prettyPath)&&l(t.prettyPath)===r)return u(e);return null}let p=null,ee=new Set([`chat`,`files`,`tasks`,`pages`,`settings`,`tools`,`channels`]);function m(){p=null}typeof globalThis<`u`&&(globalThis.__applyBasePathCacheReset=m);function h(){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=te();return!n||n===`/`?``:n.replace(/^\/+|\/+$/g,``).replace(/[^a-zA-Z0-9_-]/g,`-`)}function te(){if(p!==null)return p;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 p=`/`,p;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 p=`/`,p;if(!n)return p=`/`+e[0]+`/`,p}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+=`/`),p=t,p}catch{}}}let t=e.pathname||`/`;if(t===`/`)return p=`/`,p;let n=t.split(`/`).filter(Boolean),r=n.findIndex(e=>ee.has(e.toLowerCase()));return r>=0?r===0?(p=`/`,p):(p=`/`+n.slice(0,r).join(`/`)+`/`,p):f(t)?(p=`/`,p):n.length===1&&!t.includes(`.`)?(p=`/`+n[0]+`/`,p):(p=`/`,p)}function ne(e){let t=e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return RegExp(`(^|\\s)@${t}\\b`,`i`)}ne(`ShadowClaw`);function g(){let e=h();return e?`shadowclaw-${e}`:`shadowclaw`}g();let _={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(_).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(_).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 v(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 y(e){if(Array.isArray(e))return e}function b(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 re(){throw TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="
|
|
1
|
+
<!doctype html><html lang="en"><head><title>ShadowClaw</title><meta charset="utf-8"><meta name="revision" content="b1cf3de96d967a8393a81ef9dd2c91a54ec79bec" /><meta name="version" content="1.41.1" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><meta name="description" content="ShadowClaw - multi-runtime AI assistant." /><link rel="canonical" href="https://xt-ml.github.io/shadow-claw/"><meta property="og:title" content="ShadowClaw"><meta property="og:description" content="Multi-runtime 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="Multi-runtime 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":"Multi-runtime 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 multi-runtime 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 - multi-runtime AI assistant.","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"},"agent":{"defaultProvider":"transformers_js_local","defaultModel":"onnx-community\u002fgemma-3-1b-it-ONNX-GQA"},"customElements":{"allowedElements":["block-garden","block-garden-option","block-garden-select"],"allowedDomains":["kherrick.github.io","cdn.jsdelivr.net"]},"security":{"connectSrc":["'self'","blob:","data:","https:\u002f\u002fkherrick.github.io","https:\u002f\u002fcdn.jsdelivr.net"]}}</script>
|
|
2
2
|
<script>var ShadowClawThemeInit=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);function n(){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 r=`__shadowClawDefaultTrustedTypesPolicyState`,i=`default`,a=`shadowclaw-sandbox`;function o(){let e=globalThis;return e[r]||(e[r]={initialized:!1,policy:null}),e[r]}function s(){let e=o();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(i);if(n){e.policy=n;return}}let n=!1;if(typeof t.getPolicyNames==`function`){let e=t.getPolicyNames();Array.isArray(e)&&e.includes(i)&&(n=!0)}if(n){if(typeof t.getPolicy==`function`){let n=t.getPolicy(a);if(n){e.policy=n;return}}e.policy=t.createPolicy(a,{createHTML:e=>e,createScriptURL:e=>e});return}e.policy=t.createPolicy(i,{createHTML:e=>e,createScriptURL:e=>e})}catch{typeof t.getPolicy==`function`&&(e.policy=t.getPolicy(i)??t.getPolicy(a)??null)}}}new class{loading=!1;models=new Map;getModelInfo(e){if(!e||typeof e!=`string`)return null;let t=e.toLowerCase().trim(),n=this.models.get(t);if(n)return n;if(t.includes(`/`)){let e=t.split(`/`).pop();if(e&&this.models.has(e))return this.models.get(e)}else for(let[e,n]of this.models.entries())if(e.endsWith(`/${t}`))return n;if(t.includes(`:`)){let e=t.split(`:`)[0],n=this.getModelInfo(e);if(n)return n}return 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),d=typeof e.supports_prompt_caching==`boolean`?e.supports_prompt_caching:typeof e.supportsPromptCaching==`boolean`?e.supportsPromptCaching:this.supportedParametersInclude(e,`cache_control`,`prompt_caching`)?!0:void 0;!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},...d!==void 0&&{supportsPromptCaching:d}})}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 c(e){return e.replace(/^\/+|\/+$/g,``)}function l(e){let t=c(e);return t.endsWith(`/index.html`)?t=t.slice(0,-11):t.endsWith(`index.html`)&&(t=t.slice(0,-10)),c(t)}function u(e){let t=c(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 d(){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 f(e,t){let n=t||d();if(!n||!n.routes)return null;let r=l(e);for(let[e,t]of Object.entries(n.routes))if(!(!t||!t.prettyPath)&&l(t.prettyPath)===r)return u(e);return null}let p=null,ee=new Set([`chat`,`files`,`tasks`,`pages`,`settings`,`tools`,`channels`]);function m(){p=null}typeof globalThis<`u`&&(globalThis.__applyBasePathCacheReset=m);function h(){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=te();return!n||n===`/`?``:n.replace(/^\/+|\/+$/g,``).replace(/[^a-zA-Z0-9_-]/g,`-`)}function te(){if(p!==null)return p;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 p=`/`,p;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 p=`/`,p;if(!n)return p=`/`+e[0]+`/`,p}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+=`/`),p=t,p}catch{}}}let t=e.pathname||`/`;if(t===`/`)return p=`/`,p;let n=t.split(`/`).filter(Boolean),r=n.findIndex(e=>ee.has(e.toLowerCase()));return r>=0?r===0?(p=`/`,p):(p=`/`+n.slice(0,r).join(`/`)+`/`,p):f(t)?(p=`/`,p):n.length===1&&!t.includes(`.`)?(p=`/`+n[0]+`/`,p):(p=`/`,p)}function ne(e){let t=e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return RegExp(`(^|\\s)@${t}\\b`,`i`)}ne(`ShadowClaw`);function g(){let e=h();return e?`shadowclaw-${e}`:`shadowclaw`}g();let _={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(_).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(_).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 v(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 y(e){if(Array.isArray(e))return e}function b(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 re(){throw TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Theming, Stylesheets & Layout Customization
|
|
2
|
+
|
|
3
|
+
> Architecture, configuration, and styling patterns for theming ShadowClaw sites, customizing layout tokens, and styling slotted components across light and shadow DOM boundaries.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
ShadowClaw provides a layered theming and templating system designed for both standalone AI assistant deployments and published static documentation hubs or personal sites. The styling architecture operates across two distinct DOM domains:
|
|
10
|
+
|
|
11
|
+
1. **Shadow DOM (Component Internals)**:
|
|
12
|
+
- Built-in web components (`<shadow-claw>`, `<shadow-claw-pages>`, `<shadow-claw-chat>`, `<shadow-claw-dialog>`, etc.) render inside open Shadow Roots with pre-rendered Declarative Shadow DOM (DSD) templates.
|
|
13
|
+
- These components are insulated from external CSS rules, but inherit and reflect global **CSS Custom Properties** (`--shadow-claw-*`).
|
|
14
|
+
2. **Light DOM (Slotted Content & Custom Shell)**:
|
|
15
|
+
- Slotted elements injected into named slots (`slot="sidebar"`, `slot="sidebar-nav"`, `slot="sidebar-content"`, `slot="sidebar-footer"`, `slot="header-actions-logo"`, `slot="logo"`) live directly in the host page's Light DOM.
|
|
16
|
+
- These elements are styled using standard CSS selectors defined in the site's custom stylesheet (`theme.stylesheet`).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Configuration (`shadow-claw.config.json`)
|
|
21
|
+
|
|
22
|
+
Custom theming is declared in the root configuration file (`shadow-claw.config.json` or legacy `site-config.json`):
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"theme": {
|
|
27
|
+
"stylesheet": "pages/main/theme.css"
|
|
28
|
+
},
|
|
29
|
+
"sidebar": {
|
|
30
|
+
"pagesHidden": true,
|
|
31
|
+
"chatHidden": true,
|
|
32
|
+
"tasksHidden": true,
|
|
33
|
+
"filesHidden": true,
|
|
34
|
+
"defaultPage": "pages"
|
|
35
|
+
},
|
|
36
|
+
"branding": {
|
|
37
|
+
"titleText": "My Custom Site",
|
|
38
|
+
"faviconPath": "pages/resources/favicon.ico",
|
|
39
|
+
"appleTouchIconPath": "pages/resources/icon-192x192.png"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `theme.stylesheet`
|
|
45
|
+
|
|
46
|
+
- **Build-Time Processing**: When `theme.stylesheet` is defined:
|
|
47
|
+
- `src/cli/site-config/apply.ts` locates the source stylesheet.
|
|
48
|
+
- The stylesheet is copied into the distribution directory (`dist/public/`).
|
|
49
|
+
- An HTML link tag (`<link rel="stylesheet" href="..." />`) is injected immediately before the closing `</head>` in `dist/public/index.html` and across all pre-rendered pretty path `index.html` files.
|
|
50
|
+
- **Path Resolution & Asset Flattening**:
|
|
51
|
+
- Stylesheets located in resource folders (`pages/resources/`, `pages/deps/`, `resources/`, `deps/`, `pages/assets/`, or `pages/main/assets/`) are flattened to the root of the distribution directory, and their href prefix is automatically resolved.
|
|
52
|
+
- Custom paths such as `pages/main/theme.css` retain their posix path relative to `<base href="/">`.
|
|
53
|
+
- External stylesheets starting with `http://`, `https://`, or `//` are preserved verbatim.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## CSS Custom Properties (Theme Tokens)
|
|
58
|
+
|
|
59
|
+
ShadowClaw exposes CSS variables across several functional categories:
|
|
60
|
+
|
|
61
|
+
### 1. Layout & Shell Visibility Overrides
|
|
62
|
+
|
|
63
|
+
These tokens allow a custom site to selectively hide or reconfigure default built-in chrome without modifying core component templates:
|
|
64
|
+
|
|
65
|
+
| CSS Custom Property | Default | Purpose / Usage |
|
|
66
|
+
| :----------------------------------------------- | :------- | :------------------------------------------------------------------------------------------ |
|
|
67
|
+
| `--shadow-claw-sidebar-footer-display` | `block` | Set to `none` to hide the default sidebar footer containing the Settings button. |
|
|
68
|
+
| `--shadow-claw-header-main-toggle-display` | `block` | Set to `none` to hide the top-left main app drawer / hamburger toggle button in the header. |
|
|
69
|
+
| `--shadow-claw-page-header-display` | `flex` | Set to `none` to hide the `<shadow-claw-page-header>` component entirely. |
|
|
70
|
+
| `--shadow-claw-pages-dropdown-container-display` | `flex` | Set to `none` to hide the page navigation dropdown / breadcrumb selector. |
|
|
71
|
+
| `--shadow-claw-article-header-margin-bottom` | `1.5rem` | Controls the bottom margin beneath markdown article headers. |
|
|
72
|
+
|
|
73
|
+
### 2. Design Tokens (Colors, Typography & Radii)
|
|
74
|
+
|
|
75
|
+
Defined globally in `index.css`:
|
|
76
|
+
|
|
77
|
+
```css
|
|
78
|
+
:root {
|
|
79
|
+
/* Fonts */
|
|
80
|
+
--shadow-claw-font-sans:
|
|
81
|
+
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
82
|
+
--shadow-claw-font-mono: "Fira Code", "Courier New", monospace;
|
|
83
|
+
|
|
84
|
+
/* Colors (Light Mode) */
|
|
85
|
+
--shadow-claw-bg-primary: #f8fafc;
|
|
86
|
+
--shadow-claw-bg-secondary: #f1f5f9;
|
|
87
|
+
--shadow-claw-bg-tertiary: #e2e8f0;
|
|
88
|
+
--shadow-claw-border-color: #e2e8f0;
|
|
89
|
+
--shadow-claw-text-primary: #0f172a;
|
|
90
|
+
--shadow-claw-text-secondary: #475569;
|
|
91
|
+
--shadow-claw-text-tertiary: #64748b;
|
|
92
|
+
--shadow-claw-link: #1e40af;
|
|
93
|
+
--shadow-claw-link-hover: #334155;
|
|
94
|
+
--shadow-claw-accent-primary: #334155;
|
|
95
|
+
--shadow-claw-accent-hover: #1e293b;
|
|
96
|
+
|
|
97
|
+
/* Border Radii */
|
|
98
|
+
--shadow-claw-radius-s: 0.5rem;
|
|
99
|
+
--shadow-claw-radius-m: 0.75rem;
|
|
100
|
+
--shadow-claw-radius-l: 1.25rem;
|
|
101
|
+
--shadow-claw-radius-pill: 62.5rem;
|
|
102
|
+
|
|
103
|
+
/* Transitions */
|
|
104
|
+
--shadow-claw-duration-min: 150ms;
|
|
105
|
+
--shadow-claw-duration-regular: 300ms;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
In dark mode (`html.dark-mode`), variables automatically adapt or can be overridden via `@media (prefers-color-scheme: dark)` or `html.dark-mode` selectors.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Slotted Navigation & Viewport Containment
|
|
114
|
+
|
|
115
|
+
### Slotted Sidebar Elements
|
|
116
|
+
|
|
117
|
+
Custom sidebars can be supplied via `pages/main/sidebar.html`, `sidebar.slotHtml`, or declarative `sidebar.sections` in `shadow-claw.config.json`. These render into the light DOM within `<shadow-claw>`:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<div slot="sidebar" class="sidebar-custom-sections">
|
|
121
|
+
<section class="sidebar-section">
|
|
122
|
+
<div class="sidebar-section-header"><span>Projects</span></div>
|
|
123
|
+
<div class="sidebar-item">
|
|
124
|
+
<a href="/project-a" class="sidebar-link">Project A</a>
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
</div>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Viewport Scroll Containment
|
|
131
|
+
|
|
132
|
+
To prevent dual scrollbars when using a custom slotted sidebar alongside scrollable main content, enforce viewport containment on the outer document:
|
|
133
|
+
|
|
134
|
+
```css
|
|
135
|
+
/* Prevent the outer SPA window from producing a secondary scrollbar */
|
|
136
|
+
html:has(shadow-claw),
|
|
137
|
+
body:has(shadow-claw) {
|
|
138
|
+
height: 100%;
|
|
139
|
+
height: 100dvh;
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Ensure the slotted sidebar container scrolls independently */
|
|
144
|
+
.sidebar-custom-sections {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: 0.5rem;
|
|
148
|
+
max-height: calc(100vh - 4rem);
|
|
149
|
+
max-height: calc(100dvh - 4rem);
|
|
150
|
+
overflow-y: auto;
|
|
151
|
+
padding: 0.5rem;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Active Link Synchronization
|
|
156
|
+
|
|
157
|
+
Slotted anchor elements (`.sidebar-link`, `[slot^="sidebar"] a`) automatically synchronize `.active` classes and `aria-current="page"` via `syncSlottedSidebarActiveLinks` on:
|
|
158
|
+
|
|
159
|
+
- History API events (`pushState`, `replaceState`, `popstate`).
|
|
160
|
+
- Modern Navigation API transitions (`navigate`).
|
|
161
|
+
- Route changes dispatched by the client-side router (`app-routes.ts`).
|
|
162
|
+
|
|
163
|
+
On viewports narrower than `896px` (mobile drawer), clicking any slotted link automatically collapses the navigation drawer.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Sandboxed Preview Iframe Theme Sync
|
|
168
|
+
|
|
169
|
+
When viewing markdown documents or HTML files in the Pages preview or File Viewer, content renders inside a sandboxed iframe. ShadowClaw ensures styling consistency across the iframe boundary via `src/ui/iframe-theme.ts`:
|
|
170
|
+
|
|
171
|
+
1. **Automatic Stylesheet Inclusion**:
|
|
172
|
+
- `getIframeThemeStylesheetLink(siteConfig)` inspects the active site configuration and injects the configured `theme.stylesheet` into the iframe's `<head>`.
|
|
173
|
+
2. **Computed CSS Property Reflection**:
|
|
174
|
+
- `getIframeThemeStyleHtml()` reads all computed `--*` CSS custom properties from `document.documentElement` and mirrors them into the iframe's `:root` style block.
|
|
175
|
+
3. **Color Scheme & Class Mirroring**:
|
|
176
|
+
- The iframe root receives the matching `.dark-mode` or `.light-mode` class and `color-scheme: dark | light` to prevent Flash of Unstyled Content (FOUC).
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Complete Theming Example
|
|
181
|
+
|
|
182
|
+
Below is a complete, working `pages/main/theme.css` example implementing custom layout tokens, viewport containment, and slotted sidebar styling:
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
:root {
|
|
186
|
+
/* Hide unwanted built-in shell elements */
|
|
187
|
+
--shadow-claw-header-main-toggle-display: none;
|
|
188
|
+
--shadow-claw-page-header-display: none;
|
|
189
|
+
--shadow-claw-pages-dropdown-container-display: none;
|
|
190
|
+
--shadow-claw-sidebar-footer-display: none;
|
|
191
|
+
|
|
192
|
+
/* Custom Spacing */
|
|
193
|
+
--shadow-claw-article-header-margin-bottom: 1.5rem;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Contain the outer viewport */
|
|
197
|
+
html:has(shadow-claw),
|
|
198
|
+
body:has(shadow-claw) {
|
|
199
|
+
height: 100%;
|
|
200
|
+
height: 100dvh;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Slotted Sidebar Scroll & Layout */
|
|
205
|
+
.sidebar-custom-sections {
|
|
206
|
+
--shadow-claw-radius-m: var(--shadow-claw-radius-s, 0.375rem);
|
|
207
|
+
display: flex;
|
|
208
|
+
flex-direction: column;
|
|
209
|
+
gap: 0.5rem;
|
|
210
|
+
max-height: calc(100vh - 4rem);
|
|
211
|
+
max-height: calc(100dvh - 4rem);
|
|
212
|
+
overflow-y: auto;
|
|
213
|
+
padding: 0.5rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.sidebar-section {
|
|
217
|
+
display: flex;
|
|
218
|
+
flex-direction: column;
|
|
219
|
+
gap: 0.125rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.sidebar-section-header {
|
|
223
|
+
color: var(--shadow-claw-text-secondary, #888);
|
|
224
|
+
font-size: var(--shadow-claw-font-size-sm, 0.875rem);
|
|
225
|
+
font-weight: 500;
|
|
226
|
+
padding: 0.25rem 0.5rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.sidebar-link {
|
|
230
|
+
color: var(--shadow-claw-text-primary);
|
|
231
|
+
text-decoration: none;
|
|
232
|
+
padding: 0.375rem 0.5rem;
|
|
233
|
+
border-radius: var(--shadow-claw-radius-s, 0.375rem);
|
|
234
|
+
transition: background-color var(--shadow-claw-duration-min) ease;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.sidebar-link:hover,
|
|
238
|
+
.sidebar-link.active {
|
|
239
|
+
background-color: var(--shadow-claw-bg-secondary);
|
|
240
|
+
color: var(--shadow-claw-link);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
@@ -431,6 +431,7 @@ export const TIME_MINUTES_ONE = 60000;
|
|
|
431
431
|
- **Proxy Security Testing**: Verify that the `/proxy` endpoint rejects non-HTTP/S schemes (`file:`, `ftp:`, etc.) with `400` and blocks private/loopback IP targets (`localhost`, `127.*`, `10.*`, `192.168.*`, `169.254.*`) with `403`. Use the unit tests in `src/server/utils/proxy-helpers.test.ts` for scheme and SSRF guard coverage; E2E tests should verify that the authenticated service-worker format (body-based JSON with `Content-Type: application/json`) is still allowed to reach local tool servers.
|
|
432
432
|
- **Page Navigation Testing**: When testing pages or file viewer navigation, ensure keyboard events (`ArrowLeft`/`ArrowRight`) are properly guarded and do not trigger when the active element is an input or content-editable. Swipe gestures can be simulated using mouse events (`mousedown`, `mousemove`, `mouseup`) or touch events; ensure drag/text-selection does not trigger a false swipe. Assert that same-origin links are validated via `isPossibleAppRoute` so non-app routes (e.g. standalone demo subpaths) bypass SPA navigation and trigger native browser navigation (`window.open`). Verify root pretty path resolution (mapping `/` and `/index.html` to `{ page: "pages", groupId: "br:main", path: "index.html" }` and resolving `{ page: "pages" }` back to `/`), and assert that in-feed scroll / scroll-spy `replaceState` navigations with `inFeedScroll: true`, `scrollSpy: true`, or `suppressRouter: true` do not trigger router route changes or re-renders.
|
|
433
433
|
- **Sidebar Visibility & Fallback Testing**: When verifying sidebar toggle controls, assert that hiding the currently active view triggers automatic navigation to the next available visible view (`getDefaultSidebarPage`). For custom slotted sidebar links (`[slot="sidebar"]`, `[slot="sidebar-nav"]`, etc.), assert that link elements accurately reflect `.active` classes and `aria-current="page"` across route navigations, and clicking slotted links on mobile viewports (<896px) auto-closes the sidebar drawer.
|
|
434
|
+
- **Theming & Stylesheet Testing**: When testing custom themed deployments or template sites (`theme.stylesheet`), assert that `<link rel="stylesheet">` tags are placed before `</head>` in the document head without corrupting inlined scripts. Verify that layout tokens (e.g. `--shadow-claw-sidebar-footer-display: none;`, `--shadow-claw-header-main-toggle-display: none;`) correctly apply to shadow DOM elements, slotted `.sidebar-custom-sections` elements scroll independently within `max-height: calc(100dvh - 4rem)`, and preview iframes correctly load the custom stylesheet and inherit computed `--*` CSS custom properties.
|
|
434
435
|
- **Custom Element Guard Testing**: When testing custom elements in E2E or unit suites, verify that registering unapproved custom element tags throws a security error and DOM injection of unapproved custom element tags results in immediate removal by the DOM mutation guard. For custom element script descriptors (`customElements.scripts`), verify that object entries (`{ src, hasInit }`) are correctly parsed alongside legacy URLs, and `init()` is executed exclusively when `hasInit: true`.
|
|
435
436
|
- **Sandboxed Iframe & Storage Proxy Testing**: Verify preview iframe navigation and storage bridges in `file-viewer-preview-bridge.js` and `iframe-storage-bridge.js`. Assert that link clicks and programmatic navigation (`location.href`, `location.assign()`, `location.replace()`) relay navigation messages to the parent frame via `postMessage`, rebuilding the iframe `srcdoc` with injected `location.search` parameters before custom element scripts execute. Assert that `IndexedDB` and `localStorage` CRUD operations are proxied via `shadow-claw-storage-proxy` to parent storage, `showOpenFilePicker` and `showSaveFilePicker` degrade gracefully to input/download triggers, ServiceWorker/caches getters are trapped without `SecurityError` rejections, and `IframeBroadcastProxy` relays BroadcastChannel messages while suppressing command echo feedback loops via `isRelayingCommandFromParent`.
|
|
436
437
|
- **Peer & Room File Sharing Testing**: When verifying file actions in `<shadow-claw-files>` and `<shadow-claw-file-viewer>`, ensure tests assert that peer transfer options (`.files__send-peer`, `.modal-share-dropdown`) appear strictly when the conversation is a peer (`peer:...`) or room (`room:...`), and remain hidden in non-peer conversations. Verify that clicking peer transfer options correctly calls the underlying channel/store methods, shows typing indicators during transfer, and renders the sent attachment in the local chat stream.
|