@m13v/s4l 1.7.5-rc.7 → 1.7.5-rc.9
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/mcp/dist/index.js +6 -6
- package/mcp/dist/panel.html +2 -3
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_menubar.py +21 -12
- package/mcp/package.json +1 -1
- package/package.json +1 -1
- package/scripts/claude_job.py +4 -1
- package/scripts/stats.py +20 -0
package/mcp/dist/index.js
CHANGED
|
@@ -2814,9 +2814,9 @@ tool("posting_volume", {
|
|
|
2814
2814
|
title: "Read or set posting volume (Aggressive / Steady / Chill)",
|
|
2815
2815
|
description: "Read or set this install's posting-volume mode, the quality bar that decides how many drafts " +
|
|
2816
2816
|
"per day the twitter cycle produces. Three modes, shown to users as Aggressive (~100+ posts/day), " +
|
|
2817
|
-
"Steady (~30/day), and Chill (~5/day, only the very best
|
|
2818
|
-
"
|
|
2819
|
-
"
|
|
2817
|
+
"Steady (~30/day, the default every install starts on), and Chill (~5/day, only the very best " +
|
|
2818
|
+
"candidates). Internally the modes are high|medium|low and both spellings are accepted. " +
|
|
2819
|
+
"action:'get' returns the current mode plus per-mode estimated " +
|
|
2820
2820
|
"posts/day computed from this install's own recent candidate pool (show those numbers with the " +
|
|
2821
2821
|
"Aggressive/Steady/Chill names when the user is choosing). Use when the user asks to post more, " +
|
|
2822
2822
|
"post less, slow down, be more aggressive, chill out, raise the quality bar, or change posting " +
|
|
@@ -2825,9 +2825,9 @@ tool("posting_volume", {
|
|
|
2825
2825
|
inputSchema: {
|
|
2826
2826
|
action: z.enum(["get", "set"]).default("get").describe("get = read mode + rates; set = change it"),
|
|
2827
2827
|
mode: z
|
|
2828
|
-
.enum(["aggressive", "steady", "chill", "high", "medium", "low"
|
|
2828
|
+
.enum(["aggressive", "steady", "chill", "high", "medium", "low"])
|
|
2829
2829
|
.optional()
|
|
2830
|
-
.describe("Required for action:'set'. aggressive=high, steady=medium, chill=low
|
|
2830
|
+
.describe("Required for action:'set'. aggressive=high, steady=medium, chill=low."),
|
|
2831
2831
|
},
|
|
2832
2832
|
}, async (args) => {
|
|
2833
2833
|
const action = args.action || "get";
|
|
@@ -2835,7 +2835,7 @@ tool("posting_volume", {
|
|
|
2835
2835
|
const ALIAS = { aggressive: "high", steady: "medium", chill: "low" };
|
|
2836
2836
|
if (action === "set") {
|
|
2837
2837
|
if (!args.mode) {
|
|
2838
|
-
return jsonContent({ error: "mode is required for action:'set' (aggressive|steady|chill
|
|
2838
|
+
return jsonContent({ error: "mode is required for action:'set' (aggressive|steady|chill)" });
|
|
2839
2839
|
}
|
|
2840
2840
|
const stored = ALIAS[String(args.mode)] || String(args.mode);
|
|
2841
2841
|
const r = await runPython("scripts/s4l_posting_mode.py", ["set", stored], {
|
package/mcp/dist/panel.html
CHANGED
|
@@ -70,7 +70,7 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
70
70
|
- omitted: host decides border`)});m({method:u("ui/request-display-mode"),params:m({mode:st.describe("The display mode being requested.")})});var Eh=m({mode:st.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),Dh=U([u("model"),u("app")]).describe("Tool visibility scope - who can access the tool.");m({resourceUri:d().optional(),visibility:x(Dh).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
71
71
|
- "model": Tool visible to and callable by the agent
|
|
72
72
|
- "app": Tool callable by the app from this server only`),csp:Te().optional(),permissions:Te().optional()});m({mimeTypes:x(d()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')});m({method:u("ui/download-file"),params:m({contents:x(U([Ol,Tl])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})});m({method:u("ui/message"),params:m({role:u("user").describe('Message role, currently only "user" is supported.'),content:x(jt).describe("Message content blocks (text, image, etc.).")})});m({method:u("ui/notifications/sandbox-resource-ready"),params:m({html:d().describe("HTML content to load into the inner iframe."),sandbox:d().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:xo.optional().describe("CSP configuration from resource metadata."),permissions:jo.optional().describe("Sandbox permissions from resource metadata.")})});var Rh=m({method:u("ui/notifications/tool-result"),params:Qn.describe("Standard MCP tool execution result.")}),Cl=m({toolInfo:m({id:kt.optional().describe("JSON-RPC id of the tools/call request."),tool:zo.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:yh.optional().describe("Current color theme preference."),styles:Oh.optional().describe("Style configuration for theming the app."),displayMode:st.optional().describe("How the UI is currently displayed."),availableDisplayModes:x(st).optional().describe("Display modes the host supports."),containerDimensions:U([m({height:O().describe("Fixed container height in pixels.")}),m({maxHeight:U([O(),it()]).optional().describe("Maximum container height in pixels.")})]).and(U([m({width:O().describe("Fixed container width in pixels.")}),m({maxWidth:U([O(),it()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
73
|
-
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d().optional().describe("User's timezone in IANA format."),userAgent:d().optional().describe("Host application identifier."),platform:U([u("web"),u("desktop"),u("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:m({touch:H().optional().describe("Whether the device supports touch input."),hover:H().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:m({top:O().describe("Top safe area inset in pixels."),right:O().describe("Right safe area inset in pixels."),bottom:O().describe("Bottom safe area inset in pixels."),left:O().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Ch=m({method:u("ui/notifications/host-context-changed"),params:Cl.describe("Partial context update containing only changed fields.")});m({method:u("ui/update-model-context"),params:m({content:x(jt).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:A(d(),J().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});m({method:u("ui/initialize"),params:m({appInfo:Kn.describe("App identification (name and version)."),appCapabilities:Uh.describe("Features and capabilities this app provides."),protocolVersion:d().describe("Protocol version this app supports.")})});var Zh=m({protocolVersion:d().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Kn.describe("Host application identification and version."),hostCapabilities:Ph.describe("Features and capabilities provided by the host."),hostContext:Cl.describe("Rich context about the host environment.")}).passthrough(),Ah={target:"draft-2020-12"};async function ta(e,n){let r=e["~standard"];if(r.jsonSchema)return r.jsonSchema[n](Ah);if(r.vendor==="zod"){let{z:o}=await td(()=>Promise.resolve().then(()=>zp),void 0,import.meta.url);return o.toJSONSchema(e,{io:n})}throw Error(`Schema (vendor: ${r.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function na(e,n,r=""){let o=await e["~standard"].validate(n);if(o.issues){let t=o.issues.map(i=>{var s;let a=(s=i.path)==null?void 0:s.map(c=>typeof c=="object"?c.key:c).join(".");return a?`${a}: ${i.message}`:i.message}).join("; ");throw Error(r+t)}return o.value}function Lh(e){let n=document.documentElement;n.setAttribute("data-theme",e),n.style.colorScheme=e}function Mh(e,n=document.documentElement){for(let[r,o]of Object.entries(e))o!==void 0&&n.style.setProperty(r,o)}function qh(e){if(document.getElementById("__mcp-host-fonts"))return;let n=document.createElement("style");n.id="__mcp-host-fonts",n.textContent=e,document.head.appendChild(n)}const Bt=class Bt extends gh{constructor(r,o={},t={autoResize:!0}){super(t);C(this,"_appInfo");C(this,"_capabilities");C(this,"options");C(this,"_hostCapabilities");C(this,"_hostInfo");C(this,"_hostContext");C(this,"_registeredTools",{});C(this,"_initializedSent",!1);C(this,"eventSchemas",{toolinput:zh,toolinputpartial:xh,toolresult:Rh,toolcancelled:jh,hostcontextchanged:Ch});C(this,"_everHadListener",new Set);C(this,"_toolHandlersInitialized",!1);C(this,"_onteardown");C(this,"_oncalltool");C(this,"_onlisttools");C(this,"sendOpenLink",this.openLink);this._appInfo=r,this._capabilities=o,this.options=t,t.allowUnsafeEval||ee({jitless:!0}),this.setRequestHandler(Gn,i=>(console.log("Received ping:",i.params),{})),this.setEventHandler("hostcontextchanged",void 0)}_assertInitialized(r){var t;if(this._initializedSent)return;let o=`[ext-apps] App.${r}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if((t=this.options)!=null&&t.strict)throw Error(o);console.warn(`${o}. This will throw in a future release.`)}_assertHandlerTiming(r){var t;if(!Bt.ONE_SHOT_EVENTS.has(r)||this._everHadListener.has(r)||(this._everHadListener.add(r),!this._initializedSent))return;let o=`[ext-apps] "${String(r)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if((t=this.options)!=null&&t.strict)throw Error(o);console.warn(o)}setEventHandler(r,o){o&&this._assertHandlerTiming(r),super.setEventHandler(r,o)}addEventListener(r,o){this._assertHandlerTiming(r),super.addEventListener(r,o)}onEventDispatch(r,o){r==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...o})}registerCapabilities(r){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._capabilities=hh(this._capabilities,r)}registerTool(r,o,t){if(this._registeredTools[r])throw Error(`Tool ${r} is already registered`);let i=this,a=()=>{var p;i._initializedSent&&((p=i._capabilities.tools)!=null&&p.listChanged)&&i.sendToolListChanged()},s=o.inputSchema!==void 0,c={title:o.title,description:o.description,inputSchema:o.inputSchema,outputSchema:o.outputSchema,annotations:o.annotations,_meta:o._meta,enabled:!0,enable(){this.enabled=!0,a()},disable(){this.enabled=!1,a()},update(p){Object.assign(this,p),a()},remove(){i._registeredTools[r]===c&&(delete i._registeredTools[r],a())},handler:async(p,h)=>{if(!c.enabled)throw Error(`Tool ${r} is disabled`);let f;if(s){let _=c.inputSchema,w=_?await na(_,p??{},`Invalid input for tool ${r}: `):p??{};f=await t(w,h)}else f=await t(h);return c.outputSchema&&!f.isError&&(f.structuredContent=await na(c.outputSchema,f.structuredContent,`Invalid output for tool ${r}: `)),f}};return this._registeredTools[r]=c,!this._capabilities.tools&&!this.transport&&this.registerCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),a(),c}ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(r,o)=>{let t=this._registeredTools[r.name];if(!t)throw Error(`Tool ${r.name} not found`);return t.handler(r.arguments,o)},this.onlisttools=async(r,o)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([t,i])=>i.enabled).map(async([t,i])=>{let a={name:t,title:i.title,description:i.description,inputSchema:i.inputSchema?await ta(i.inputSchema,"input"):{type:"object",properties:{}}};return i.outputSchema&&(a.outputSchema=await ta(i.outputSchema,"output")),i.annotations&&(a.annotations=i.annotations),i._meta&&(a._meta=i._meta),a}))}))}async sendToolListChanged(r={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:r})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(r){this.setEventHandler("toolinput",r)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(r){this.setEventHandler("toolinputpartial",r)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(r){this.setEventHandler("toolresult",r)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(r){this.setEventHandler("toolcancelled",r)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(r){this.setEventHandler("hostcontextchanged",r)}get onteardown(){return this._onteardown}set onteardown(r){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,r),this._onteardown=r,this.replaceRequestHandler(Th,(o,t)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(o.params,t)})}get oncalltool(){return this._oncalltool}set oncalltool(r){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,r),this._oncalltool=r,this.replaceRequestHandler(Ul,(o,t)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,t)})}get onlisttools(){return this._onlisttools}set onlisttools(r){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,r),this._onlisttools=r,this.replaceRequestHandler(Pl,(o,t)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(o.params,t)})}assertCapabilityForMethod(r){var o;switch(r){case"sampling/createMessage":if(!((o=this._hostCapabilities)!=null&&o.sampling))throw Error(`Host does not support sampling (required for ${r})`);break}}assertRequestHandlerCapability(r){switch(r){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${r})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${r} registered`)}}assertNotificationCapability(r){}assertTaskCapability(r){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(r){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(r,o){if(this._assertInitialized("callServerTool"),typeof r=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${r}"). Did you mean: callServerTool({ name: "${r}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:r},Qn,{onprogress:()=>{},resetTimeoutOnProgress:!0,...o})}async readServerResource(r,o){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:r},Nl,o)}async listServerResources(r,o){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:r},jl,o)}async createSamplingMessage(r,o){this._assertInitialized("createSamplingMessage");let t=r.tools?Rl:Dl;return await this.request({method:"sampling/createMessage",params:r},t,o)}sendMessage(r,o){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:r},Ih,o)}sendLog(r){return this.notification({method:"notifications/message",params:r})}updateModelContext(r,o){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:r},lo,o)}openLink(r,o){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:r},Sh,o)}downloadFile(r,o){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:r},wh,o)}requestTeardown(r={}){return this.notification({method:"ui/notifications/request-teardown",params:r})}requestDisplayMode(r,o){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:r},Eh,o)}sendSizeChanged(r){return this.notification({method:"ui/notifications/size-changed",params:r})}setupSizeChangedNotifications(){let r=!1,o=0,t=0,i=()=>{r||(r=!0,requestAnimationFrame(()=>{r=!1;let s=document.documentElement,c=s.style.height;s.style.height="max-content";let p=Math.ceil(s.getBoundingClientRect().height);s.style.height=c;let h=Math.ceil(window.innerWidth);(h!==o||p!==t)&&(o=h,t=p,this.sendSizeChanged({width:h,height:p}))}))};i();let a=new ResizeObserver(i);return a.observe(document.documentElement),a.observe(document.body),()=>a.disconnect()}async connect(r=new bh(window.parent,window.parent),o){var t;if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1,await super.connect(r);try{let i=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:vh}},Zh,o);if(i===void 0)throw Error(`Server sent invalid initialize result: ${i}`);this._hostCapabilities=i.hostCapabilities,this._hostInfo=i.hostInfo,this._hostContext=i.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,(t=this.options)!=null&&t.autoResize&&this.setupSizeChangedNotifications()}catch(i){throw this.close(),i}}};C(Bt,"ONE_SHOT_EVENTS",new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]));let dr=Bt;function ra(e){const n=e&&e.structuredContent;if(n&&typeof n=="object"){if(typeof n.snapshot=="string")try{return JSON.parse(n.snapshot)}catch{}return n}const r=(e&&e.content||[]).find(o=>o&&o.type==="text");if(r!=null&&r.text)try{return JSON.parse(r.text)}catch{return{}}return{}}class Hh{constructor(){C(this,"onhostcontextchanged");C(this,"onerror");C(this,"ontoolresult")}async connect(){var n,r;try{const[o,t]=await Promise.all([this.callServerTool({name:"project_config",arguments:{status:!0}}),this.callServerTool({name:"runtime",arguments:{action:"status"}})]),i=ra(o),a=ra(t),s=Array.isArray(i.projects)?i.projects:[],c={projects:s,projects_total:s.length,projects_ready:s.filter(p=>p&&p.ready).length,x_connected:!!i.x_connected,x_state:i.x_state||"",x_handle:i.x_handle??null,version:i.mcp_version||"",latest_version:i.latest_version??null,update_available:!!i.update_available,runtime_ready:typeof a.runtime_ready=="boolean"?a.runtime_ready:!0,runtime_provisioning:!!a.provisioning,onboarding:a.onboarding||i.onboarding};(n=this.ontoolresult)==null||n.call(this,{structuredContent:{snapshot:JSON.stringify(c)}})}catch(o){(r=this.onerror)==null||r.call(this,o)}}getHostContext(){}async callServerTool(n){const r=await fetch(`/tool/${encodeURIComponent(n.name)}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n.arguments??{})});if(!r.ok){let o=`HTTP ${r.status}`;try{o=await r.text()||o}catch{}return{isError:!0,content:[{type:"text",text:o}]}}return r.json()}async sendMessage(){return{isError:!0}}}function Fh(){return globalThis.__S4L_BRIDGE__==="http"?new Hh:new dr({name:"S4L Panel",version:"1.0.0"})}function Zl(e){const n=e.structuredContent;if(n&&typeof n=="object"){if(typeof n.snapshot=="string")try{return JSON.parse(n.snapshot)}catch{}return n}const r=(e.content||[]).find(o=>o.type==="text");if(r!=null&&r.text)try{return JSON.parse(r.text)}catch{return{_raw:r.text}}return{}}const D=e=>document.getElementById(e),Al=D("ver"),Xe=D("btn-setup"),Ht=D("btn-schedule"),Pt=D("stats-grid"),er=D("stats-toggle"),Jh=D("log"),Bh=D("install-card"),Ne=D("setup-summary"),Ll=D("onboarding-details"),Vh=D("onboarding-steps"),tr=D("onboarding-blocker"),ia=D("onboarding-count"),Wh=D("onboarding-bar-fill"),Kh=D("live-card"),Gh=D("stats-card"),oa=D("install-steps"),Ae=D("install-err"),_e=D("btn-install"),Qh=D("menubar-banner"),aa=D("btn-menubar-restart"),No=D("btn-live"),Oo=D("btn-live-stop"),sa=D("btn-live-front"),Le=D("live-status"),Ft=D("live-img"),Ml=D("switch-personal"),ql=D("switch-promo"),ve=D("switch-pause"),Yh=D("pause-name"),Xh=D("split-row"),eg=D("split-desc"),$e=D("split-slider"),Ce=D("posting-volume-select"),tg=D("posting-volume-desc"),ng=D("mode-sub"),rg=D("settings-card"),nr=D("settings-toggle"),fe=D("settings-body");let E=null,rr=!1,Ut=!1,ir=!1,ke=!1,Yn=!1,Qe=!1,De=!1,or=!1;function F(e){Jh.textContent=e}function ig(e){switch(e){case"done":return"✓";case"running":return"…";case"error":return"×";default:return"·"}}function To(e){if(!e||!Array.isArray(e.steps)){oa.innerHTML="";return}oa.innerHTML=e.steps.map(n=>{const r=n.detail&&n.status!=="pending"?` <span class="detail">${n.status==="error"?n.detail:""}</span>`:"";return`<li class="${n.status}"><span class="glyph">${ig(n.status)}</span><span>${n.label}${r}</span></li>`}).join(""),e.error?(Ae.textContent=e.error,Ae.hidden=!1):Ae.hidden=!0}const og={environment_checked:"Environment checked",runtime_ready:"Runtime ready",x_connected:"X connected",profile_scanned:"Profile scanned",project_ready:"Project ready",topics_seeded:"Topics seeded",tasks_scheduled:"Tasks scheduled"};function ag(e){switch(e){case"complete":return"✓";case"in_progress":return"…";case"blocked":return"×";default:return"·"}}function mr(){Ll.hidden=!ke,Ne.setAttribute("aria-expanded",String(ke)),Ne.classList.toggle("expanded",ke)}function Hl(e){return e?e.setup_complete!==void 0?!!e.setup_complete:!!e.runtime_ready&&(e.projects_ready||0)>0&&!!e.x_connected:!1}function sg(e){if(!e||!Array.isArray(e.milestones)){Ne.hidden=!0,Ll.hidden=!0;return}const n=Hl(E);Ne.hidden=n,n&&ke&&(ke=!1,mr());const r=e.milestones.length,o=e.milestones.filter(i=>i.status==="complete").length,t=!!e.current_blocker&&!n;Ne.classList.toggle("complete",n),Ne.classList.toggle("blocked",t),ia.hidden=n,ia.textContent=t?`${o}/${r} · needs you`:Ut?`${o}/${r} · setting up…`:`${o}/${r}`,Wh.style.width=r>0?`${Math.round(o/r*100)}%`:"0%",Vh.innerHTML=e.milestones.map(i=>{const a=og[i.id]||i.id,s=i.attempts>1?` <span class="detail">${i.attempts} attempts</span>`:"";return`<li class="${i.status}"><span class="glyph">${ag(i.status)}</span><span>${a}${s}</span></li>`}).join(""),e.current_blocker?(tr.textContent=`Current blocker: ${e.current_blocker.message}`,tr.hidden=!1,ke=!0):tr.hidden=!0,mr()}function Jt(){if(!E)return;sg(E.onboarding),Al.innerHTML=E.update_available&&E.latest_version?`v${E.version} · <button id="btn-update" class="update-btn">Update to ${E.latest_version}</button>`:`v${E.version}`;const e=!E.runtime_ready;Bh.hidden=!e,Qh.hidden=e||E.menubar_running!==!1;const n=Hl(E);Xe.hidden=n,Xe.disabled=!1,Xe.classList.toggle("primary",!n);const r=n&&(E.schedule_state==="missing"||E.schedule_state==="disabled"||E.schedule_state==="stalled");Ht.hidden=!r,Ht.classList.toggle("primary",r);const o=E.flags||(E.mode==="promotion"?{personal_brand:!1,promotion:!0}:{personal_brand:!0,promotion:!1}),t=!!o.personal_brand,i=!!o.promotion;Ml.setAttribute("aria-checked",String(t)),ql.setAttribute("aria-checked",String(i));const a=Math.round((typeof E.personal_brand_share=="number"?E.personal_brand_share:.5)*100);Xh.hidden=!(t&&i),Yn||($e.value=String(a),Vl(a)),ng.textContent=t&&i?`Both lanes on: cycles split ${a}/${100-a} personal/promotion.`:!t&&!i?"No lane on; the cycle falls back to personal brand.":"";const s=E.paused===!0;ve.setAttribute("aria-checked",String(!s)),ve.setAttribute("aria-label",s?"Resume S4L":"Pause S4L"),ve.title=s?"Resume S4L":"Pause S4L",Yh.textContent=s?"Paused":"Running",Kh.hidden=!n,Gh.hidden=!n,rg.hidden=e}function be(e){E={...E||{},...e},Jt()}function cg(e){const n=Array.isArray(e.projects)?e.projects:[];return{projects:n,projects_total:n.length,projects_ready:n.filter(r=>r.ready).length,x_connected:!!e.x_connected,x_state:e.x_state||"",x_handle:e.x_handle??null,...e.setup_complete!==void 0?{setup_complete:!!e.setup_complete}:{},version:e.mcp_version||(E==null?void 0:E.version)||"",latest_version:e.latest_version??null,update_available:!!e.update_available,mode:e.mode??(E==null?void 0:E.mode),flags:e.flags??(E==null?void 0:E.flags),personal_brand_share:e.personal_brand_share??(E==null?void 0:E.personal_brand_share),onboarding:e.onboarding}}const Ie=Fh();function Fl(e){var n,r,o;e.theme&&Lh(e.theme),(n=e.styles)!=null&&n.variables&&Mh(e.styles.variables),(o=(r=e.styles)==null?void 0:r.css)!=null&&o.fonts&&qh(e.styles.css.fonts)}Ie.onhostcontextchanged=Fl;Ie.onerror=e=>console.error(e);Ie.ontoolresult=e=>{const n=Zl(e);n&&typeof n.projects_total=="number"&&(be(n),n.runtime_ready?Jl():n.runtime_provisioning&&Po())};async function Y(e,n={}){const r=await Ie.callServerTool({name:e,arguments:n});return Zl(r)}async function ge(){F("Refreshing…");try{const[e,n]=await Promise.all([Y("project_config",{status:!0}),Y("runtime",{action:"status"}).catch(()=>({}))]);be({...cg(e),...typeof n.runtime_ready=="boolean"?{runtime_ready:n.runtime_ready}:{},...typeof n.menubar_running=="boolean"?{menubar_running:n.menubar_running}:{},...typeof n.paused=="boolean"?{paused:n.paused}:{},onboarding:n.onboarding||e.onboarding||(E==null?void 0:E.onboarding)}),E&&!E.runtime_ready&&n.provisioning&&Po(),F(""),Jl()}catch(e){F("Refresh failed: "+((e==null?void 0:e.message)||e))}}async function Po(){if(!rr){rr=!0,_e.disabled=!0,_e.textContent="Installing…";try{for(;;){const e=await Y("runtime",{action:"status"}).catch(()=>({}));if(To(e.progress??null),e.onboarding&&be({onboarding:e.onboarding}),e.runtime_ready){be({runtime_ready:!0}),F("Runtime installed; you're ready to set up."),ge();return}const n=e.progress??null;if(n&&n.done&&!n.ok){_e.disabled=!1,_e.textContent="Retry install",F("Install failed; see the step above, then Retry.");return}await new Promise(r=>setTimeout(r,1500))}}finally{rr=!1}}}async function ug(){var r;if(Ut)return;Ut=!0,Jt();const e=Date.now(),n=1200*1e3;try{for(;;){const o=await Y("runtime",{action:"status"}).catch(()=>({}));o.progress&&To(o.progress);const t={};if(typeof o.runtime_ready=="boolean"&&(t.runtime_ready=o.runtime_ready),o.onboarding&&(t.onboarding=o.onboarding),Object.keys(t).length&&be(t),(r=o.onboarding)!=null&&r.complete){await ge(),F("Setup complete.");break}if(Date.now()-e>n)break;await new Promise(i=>setTimeout(i,2e3))}}finally{Ut=!1,Jt()}}async function Jl(){try{const e=await Y("get_stats",{days:7}),n=Array.isArray(e.projects)?e.projects[0]:null,r=n==null?void 0:n.posts;if(!r){Pt.innerHTML='<div class="muted">No stats yet.</div>';return}const o=[["Posts",r.total??0],["Active",r.active??0],["Views",r.views_period_total??r.views??0],["Replies",r.comments_period_total??r.comments??0],["Clicks",r.post_clicks_period_total??0]];Pt.innerHTML=o.map(([t,i])=>`<div class="stat"><div class="n">${i}</div><div class="l">${t}</div></div>`).join("")}catch(e){Pt.innerHTML=`<div class="muted">Stats unavailable: ${(e==null?void 0:e.message)||e}</div>`}}function Xn(e,n,r){const o=e.textContent;e.disabled=!0,e.textContent=n,r().finally(()=>{e.disabled=!1,e.textContent=o,Jt()})}Xe.addEventListener("click",()=>Xn(Xe,"Starting…",async()=>{F("Asking Claude to run setup…");try{const e=await Ie.sendMessage({role:"user",content:[{type:"text",text:"Set up S4L plugin end to end now. Inspect and repair the runtime, auto-detect and connect my X session, scan my profile, discover and research my product, then infer and save a complete project with seeded search topics. Keep going without asking me to approve each safe setup step. Ask only if I must interactively sign in or no product can be identified. Keep every reply to me extremely concise: a few short sentences at most, no step-by-step narration or long status walls. If you must ask me something (e.g. the product URL), make it one short question."}]});e!=null&&e.isError?F("The host rejected the setup request — type “set up S4L” in the chat instead."):(F("Setup is running in the chat. It will only stop for an unavoidable login or missing product."),ug())}catch(e){F("Couldn’t start setup: "+((e==null?void 0:e.message)||e))}}));Ht.addEventListener("click",()=>Xn(Ht,"Setting up…",async()=>{F("Asking Claude to schedule the draft tasks for this account…"),Y("client_event",{event:"rearm_clicked",surface:"panel"}).catch(()=>{});try{const e=await Ie.sendMessage({role:"user",content:[{type:"text",text:`Set up the S4L draft autopilot schedule for this Claude account. If queue_setup is available, call it; then for s4l-worker call the host tool create_scheduled_task with taskId, cronExpression "* * * * *", notifyOnCompletion false (REQUIRED — the default true pops a notification every minute), and the prompt — read it from ~/.claude/scheduled-tasks/s4l-worker/SKILL.md (already on disk). If the task already exists, call update_scheduled_task with taskId s4l-worker and notifyOnCompletion false instead. Before calling create_scheduled_task/list_scheduled_tasks/update_scheduled_task, run (via Bash, best-effort, ignore any error and continue if the path doesn't exist): python3 ~/.social-autoposter-mcp/repo/package/scripts/mark_event.py start <that tool's name>; immediately after it returns, run the same with 'end' instead of 'start'. Do NOT redo my X connection or project setup. Keep replies to me very short.`}]});e!=null&&e.isError?F("The host rejected it — type “set up the draft schedule” in the chat instead."):F("Scheduling is running in the chat. The draft tasks will register for this account.")}catch(e){F("Couldn’t start scheduling: "+((e==null?void 0:e.message)||e))}}));function Bl(e,n){e.addEventListener("click",async()=>{if(!e.disabled){e.disabled=!0,e.setAttribute("aria-checked",String(e.getAttribute("aria-checked")!=="true"));try{const r=await Y("engagement_mode",{action:"toggle",lane:n});r&&r.flags&&be({flags:r.flags}),await ge()}catch(r){F("Couldn’t switch lane: "+((r==null?void 0:r.message)||r)),await ge()}finally{e.disabled=!1}}})}Bl(Ml,"personal_brand");Bl(ql,"promotion");ve.addEventListener("click",async()=>{if(ve.disabled)return;const e=ve.getAttribute("aria-checked")!=="true",n=e?"resume":"pause";ve.disabled=!0,ve.setAttribute("aria-checked",String(e));try{const r=await Y("pause_s4l",{action:n});F(r!=null&&r.ok?n==="pause"?"S4L paused — drafting and posting are stopped.":"S4L resumed.":`Couldn’t ${n} S4L${r!=null&&r.detail?": "+r.detail:"."}`),await ge()}catch(r){F(`Couldn’t ${n} S4L: `+((r==null?void 0:r.message)||r)),await ge()}finally{ve.disabled=!1}});function Vl(e){eg.textContent=`${e}% personal brand / ${100-e}% promotion`}$e.addEventListener("pointerdown",()=>{Yn=!0});$e.addEventListener("input",()=>{Yn=!0,Vl(Number($e.value))});$e.addEventListener("change",async()=>{const e=Number($e.value);$e.disabled=!0;try{const n=await Y("engagement_mode",{action:"split",split:e});n&&typeof n.personal_brand_share=="number"&&be({personal_brand_share:n.personal_brand_share}),await ge()}catch(n){F("Couldn’t set the lane split: "+((n==null?void 0:n.message)||n)),await ge()}finally{$e.disabled=!1,Yn=!1}});let Wl=!1;function lg(e){if(!e||e.error)return;const n=e.mode||"";Ce.value=n;const r=Array.isArray(e.rates)?e.rates:[],o={high:"Aggressive",medium:"Steady",low:"Chill"};for(const t of r){const i=Ce.querySelector(`option[value="${t.mode}"]`);if(i&&t.est_posts_per_day!==null&&t.est_posts_per_day!==void 0){const a=Number(t.est_posts_per_day),s=o[t.mode]||t.mode;i.textContent=`${s} (~${a>=10?Math.round(a):a}/day)`}}tg.textContent=n?"How many drafts per day the cycle produces. Applies from the next cycle.":"How many drafts per day the cycle produces."}async function pr(){try{const e=await Y("posting_volume",{action:"get"});Wl=!0,lg(e)}catch{}}Ce.addEventListener("change",async()=>{const e=Ce.value||"default";Ce.disabled=!0;try{const n=await Y("posting_volume",{action:"set",mode:e});n&&n.error?F("Couldn’t change posting volume: "+n.error):F(e==="default"?"Posting volume back to the default cycle setting.":`Posting volume set to ${{high:"Aggressive",medium:"Steady",low:"Chill"}[e]||e}. Applies from the next cycle.`),await pr()}catch(n){F("Couldn’t change posting volume: "+((n==null?void 0:n.message)||n)),await pr()}finally{Ce.disabled=!1}});Ne.addEventListener("click",()=>{ke=!ke,mr()});er.addEventListener("click",()=>{Qe=!Qe,Pt.hidden=!Qe,er.setAttribute("aria-expanded",String(Qe)),er.classList.toggle("expanded",Qe)});Al.addEventListener("click",e=>{const n=e.target;n&&n.id==="btn-update"&&dg()});async function dg(){if(ir)return;ir=!0;const e=document.getElementById("btn-update");e&&(e.disabled=!0,e.textContent="Updating…"),F("Installing the latest release… this can take a minute.");try{const n=await Y("runtime",{action:"update"});n.ok?(F(`Updated to ${n.latest_published||"the latest version"}. ${n.takes_effect||"Restart the client to apply."}`),e&&(e.textContent="Update installed — restart to apply")):(F("Update failed (exit "+(n.exit_code??"?")+"). Try `npx social-autoposter@latest update` in a terminal."),e&&(e.disabled=!1,e.textContent="Retry update"))}catch(n){F("Update failed: "+((n==null?void 0:n.message)||n)),e&&(e.disabled=!1,e.textContent="Retry update")}finally{ir=!1}}_e.addEventListener("click",async()=>{Ae.hidden=!0,_e.disabled=!0,_e.textContent="Starting…",F("Installing the runtime — this is a one-time download (~150MB+).");try{const e=await Y("runtime",{action:"install"});if(e.runtime_ready){be({runtime_ready:!0}),ge();return}To(e.progress??null),Po()}catch(e){_e.disabled=!1,_e.textContent="Retry install",Ae.textContent="Couldn't start install: "+((e==null?void 0:e.message)||e),Ae.hidden=!1}});aa.addEventListener("click",()=>Xn(aa,"Restarting…",async()=>{F("Restarting the S4L menu bar…");try{const e=await Y("restart_menubar");typeof e.menubar_running=="boolean"&&be({menubar_running:e.menubar_running}),F(e.menubar_running?"Menu bar restarted.":"Couldn’t confirm the menu bar came back"+(e.detail?": "+e.detail:"."))}catch(e){F("Couldn’t restart the menu bar: "+((e==null?void 0:e.message)||e))}}));const mg=["website","description","icp","voice","differentiator","search_topics","get_started_link","content_guardrails"],pg=["description","voice","search_topics","content_angle","content_guardrails"],fr={website:"Website",description:"What it does",icp:"Target audience",voice:"Voice",differentiator:"Differentiator",search_topics:"Search topics",get_started_link:"Get-started link",content_guardrails:"Content guardrails",content_angle:"Content angle"},ca=new Set(["website","description","icp","voice","differentiator","search_topics","get_started_link","content_guardrails"]),fg=new Set(["website","get_started_link"]);function et(e){const n=e.replace(/[_-]+/g," ").trim();return n?n.charAt(0).toUpperCase()+n.slice(1):e}function Kl(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function ua(e){return Array.isArray(e)&&e.every(n=>typeof n=="string")?{kind:"list",text:e.join(`
|
|
73
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d().optional().describe("User's timezone in IANA format."),userAgent:d().optional().describe("Host application identifier."),platform:U([u("web"),u("desktop"),u("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:m({touch:H().optional().describe("Whether the device supports touch input."),hover:H().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:m({top:O().describe("Top safe area inset in pixels."),right:O().describe("Right safe area inset in pixels."),bottom:O().describe("Bottom safe area inset in pixels."),left:O().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Ch=m({method:u("ui/notifications/host-context-changed"),params:Cl.describe("Partial context update containing only changed fields.")});m({method:u("ui/update-model-context"),params:m({content:x(jt).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:A(d(),J().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});m({method:u("ui/initialize"),params:m({appInfo:Kn.describe("App identification (name and version)."),appCapabilities:Uh.describe("Features and capabilities this app provides."),protocolVersion:d().describe("Protocol version this app supports.")})});var Zh=m({protocolVersion:d().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Kn.describe("Host application identification and version."),hostCapabilities:Ph.describe("Features and capabilities provided by the host."),hostContext:Cl.describe("Rich context about the host environment.")}).passthrough(),Ah={target:"draft-2020-12"};async function ta(e,n){let r=e["~standard"];if(r.jsonSchema)return r.jsonSchema[n](Ah);if(r.vendor==="zod"){let{z:o}=await td(()=>Promise.resolve().then(()=>zp),void 0,import.meta.url);return o.toJSONSchema(e,{io:n})}throw Error(`Schema (vendor: ${r.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function na(e,n,r=""){let o=await e["~standard"].validate(n);if(o.issues){let t=o.issues.map(i=>{var s;let a=(s=i.path)==null?void 0:s.map(c=>typeof c=="object"?c.key:c).join(".");return a?`${a}: ${i.message}`:i.message}).join("; ");throw Error(r+t)}return o.value}function Lh(e){let n=document.documentElement;n.setAttribute("data-theme",e),n.style.colorScheme=e}function Mh(e,n=document.documentElement){for(let[r,o]of Object.entries(e))o!==void 0&&n.style.setProperty(r,o)}function qh(e){if(document.getElementById("__mcp-host-fonts"))return;let n=document.createElement("style");n.id="__mcp-host-fonts",n.textContent=e,document.head.appendChild(n)}const Bt=class Bt extends gh{constructor(r,o={},t={autoResize:!0}){super(t);C(this,"_appInfo");C(this,"_capabilities");C(this,"options");C(this,"_hostCapabilities");C(this,"_hostInfo");C(this,"_hostContext");C(this,"_registeredTools",{});C(this,"_initializedSent",!1);C(this,"eventSchemas",{toolinput:zh,toolinputpartial:xh,toolresult:Rh,toolcancelled:jh,hostcontextchanged:Ch});C(this,"_everHadListener",new Set);C(this,"_toolHandlersInitialized",!1);C(this,"_onteardown");C(this,"_oncalltool");C(this,"_onlisttools");C(this,"sendOpenLink",this.openLink);this._appInfo=r,this._capabilities=o,this.options=t,t.allowUnsafeEval||ee({jitless:!0}),this.setRequestHandler(Gn,i=>(console.log("Received ping:",i.params),{})),this.setEventHandler("hostcontextchanged",void 0)}_assertInitialized(r){var t;if(this._initializedSent)return;let o=`[ext-apps] App.${r}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if((t=this.options)!=null&&t.strict)throw Error(o);console.warn(`${o}. This will throw in a future release.`)}_assertHandlerTiming(r){var t;if(!Bt.ONE_SHOT_EVENTS.has(r)||this._everHadListener.has(r)||(this._everHadListener.add(r),!this._initializedSent))return;let o=`[ext-apps] "${String(r)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if((t=this.options)!=null&&t.strict)throw Error(o);console.warn(o)}setEventHandler(r,o){o&&this._assertHandlerTiming(r),super.setEventHandler(r,o)}addEventListener(r,o){this._assertHandlerTiming(r),super.addEventListener(r,o)}onEventDispatch(r,o){r==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...o})}registerCapabilities(r){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._capabilities=hh(this._capabilities,r)}registerTool(r,o,t){if(this._registeredTools[r])throw Error(`Tool ${r} is already registered`);let i=this,a=()=>{var p;i._initializedSent&&((p=i._capabilities.tools)!=null&&p.listChanged)&&i.sendToolListChanged()},s=o.inputSchema!==void 0,c={title:o.title,description:o.description,inputSchema:o.inputSchema,outputSchema:o.outputSchema,annotations:o.annotations,_meta:o._meta,enabled:!0,enable(){this.enabled=!0,a()},disable(){this.enabled=!1,a()},update(p){Object.assign(this,p),a()},remove(){i._registeredTools[r]===c&&(delete i._registeredTools[r],a())},handler:async(p,h)=>{if(!c.enabled)throw Error(`Tool ${r} is disabled`);let f;if(s){let _=c.inputSchema,w=_?await na(_,p??{},`Invalid input for tool ${r}: `):p??{};f=await t(w,h)}else f=await t(h);return c.outputSchema&&!f.isError&&(f.structuredContent=await na(c.outputSchema,f.structuredContent,`Invalid output for tool ${r}: `)),f}};return this._registeredTools[r]=c,!this._capabilities.tools&&!this.transport&&this.registerCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),a(),c}ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(r,o)=>{let t=this._registeredTools[r.name];if(!t)throw Error(`Tool ${r.name} not found`);return t.handler(r.arguments,o)},this.onlisttools=async(r,o)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([t,i])=>i.enabled).map(async([t,i])=>{let a={name:t,title:i.title,description:i.description,inputSchema:i.inputSchema?await ta(i.inputSchema,"input"):{type:"object",properties:{}}};return i.outputSchema&&(a.outputSchema=await ta(i.outputSchema,"output")),i.annotations&&(a.annotations=i.annotations),i._meta&&(a._meta=i._meta),a}))}))}async sendToolListChanged(r={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:r})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(r){this.setEventHandler("toolinput",r)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(r){this.setEventHandler("toolinputpartial",r)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(r){this.setEventHandler("toolresult",r)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(r){this.setEventHandler("toolcancelled",r)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(r){this.setEventHandler("hostcontextchanged",r)}get onteardown(){return this._onteardown}set onteardown(r){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,r),this._onteardown=r,this.replaceRequestHandler(Th,(o,t)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(o.params,t)})}get oncalltool(){return this._oncalltool}set oncalltool(r){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,r),this._oncalltool=r,this.replaceRequestHandler(Ul,(o,t)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,t)})}get onlisttools(){return this._onlisttools}set onlisttools(r){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,r),this._onlisttools=r,this.replaceRequestHandler(Pl,(o,t)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(o.params,t)})}assertCapabilityForMethod(r){var o;switch(r){case"sampling/createMessage":if(!((o=this._hostCapabilities)!=null&&o.sampling))throw Error(`Host does not support sampling (required for ${r})`);break}}assertRequestHandlerCapability(r){switch(r){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${r})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${r} registered`)}}assertNotificationCapability(r){}assertTaskCapability(r){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(r){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(r,o){if(this._assertInitialized("callServerTool"),typeof r=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${r}"). Did you mean: callServerTool({ name: "${r}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:r},Qn,{onprogress:()=>{},resetTimeoutOnProgress:!0,...o})}async readServerResource(r,o){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:r},Nl,o)}async listServerResources(r,o){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:r},jl,o)}async createSamplingMessage(r,o){this._assertInitialized("createSamplingMessage");let t=r.tools?Rl:Dl;return await this.request({method:"sampling/createMessage",params:r},t,o)}sendMessage(r,o){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:r},Ih,o)}sendLog(r){return this.notification({method:"notifications/message",params:r})}updateModelContext(r,o){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:r},lo,o)}openLink(r,o){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:r},Sh,o)}downloadFile(r,o){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:r},wh,o)}requestTeardown(r={}){return this.notification({method:"ui/notifications/request-teardown",params:r})}requestDisplayMode(r,o){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:r},Eh,o)}sendSizeChanged(r){return this.notification({method:"ui/notifications/size-changed",params:r})}setupSizeChangedNotifications(){let r=!1,o=0,t=0,i=()=>{r||(r=!0,requestAnimationFrame(()=>{r=!1;let s=document.documentElement,c=s.style.height;s.style.height="max-content";let p=Math.ceil(s.getBoundingClientRect().height);s.style.height=c;let h=Math.ceil(window.innerWidth);(h!==o||p!==t)&&(o=h,t=p,this.sendSizeChanged({width:h,height:p}))}))};i();let a=new ResizeObserver(i);return a.observe(document.documentElement),a.observe(document.body),()=>a.disconnect()}async connect(r=new bh(window.parent,window.parent),o){var t;if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1,await super.connect(r);try{let i=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:vh}},Zh,o);if(i===void 0)throw Error(`Server sent invalid initialize result: ${i}`);this._hostCapabilities=i.hostCapabilities,this._hostInfo=i.hostInfo,this._hostContext=i.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,(t=this.options)!=null&&t.autoResize&&this.setupSizeChangedNotifications()}catch(i){throw this.close(),i}}};C(Bt,"ONE_SHOT_EVENTS",new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]));let dr=Bt;function ra(e){const n=e&&e.structuredContent;if(n&&typeof n=="object"){if(typeof n.snapshot=="string")try{return JSON.parse(n.snapshot)}catch{}return n}const r=(e&&e.content||[]).find(o=>o&&o.type==="text");if(r!=null&&r.text)try{return JSON.parse(r.text)}catch{return{}}return{}}class Hh{constructor(){C(this,"onhostcontextchanged");C(this,"onerror");C(this,"ontoolresult")}async connect(){var n,r;try{const[o,t]=await Promise.all([this.callServerTool({name:"project_config",arguments:{status:!0}}),this.callServerTool({name:"runtime",arguments:{action:"status"}})]),i=ra(o),a=ra(t),s=Array.isArray(i.projects)?i.projects:[],c={projects:s,projects_total:s.length,projects_ready:s.filter(p=>p&&p.ready).length,x_connected:!!i.x_connected,x_state:i.x_state||"",x_handle:i.x_handle??null,version:i.mcp_version||"",latest_version:i.latest_version??null,update_available:!!i.update_available,runtime_ready:typeof a.runtime_ready=="boolean"?a.runtime_ready:!0,runtime_provisioning:!!a.provisioning,onboarding:a.onboarding||i.onboarding};(n=this.ontoolresult)==null||n.call(this,{structuredContent:{snapshot:JSON.stringify(c)}})}catch(o){(r=this.onerror)==null||r.call(this,o)}}getHostContext(){}async callServerTool(n){const r=await fetch(`/tool/${encodeURIComponent(n.name)}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n.arguments??{})});if(!r.ok){let o=`HTTP ${r.status}`;try{o=await r.text()||o}catch{}return{isError:!0,content:[{type:"text",text:o}]}}return r.json()}async sendMessage(){return{isError:!0}}}function Fh(){return globalThis.__S4L_BRIDGE__==="http"?new Hh:new dr({name:"S4L Panel",version:"1.0.0"})}function Zl(e){const n=e.structuredContent;if(n&&typeof n=="object"){if(typeof n.snapshot=="string")try{return JSON.parse(n.snapshot)}catch{}return n}const r=(e.content||[]).find(o=>o.type==="text");if(r!=null&&r.text)try{return JSON.parse(r.text)}catch{return{_raw:r.text}}return{}}const D=e=>document.getElementById(e),Al=D("ver"),Xe=D("btn-setup"),Ht=D("btn-schedule"),Pt=D("stats-grid"),er=D("stats-toggle"),Jh=D("log"),Bh=D("install-card"),Ne=D("setup-summary"),Ll=D("onboarding-details"),Vh=D("onboarding-steps"),tr=D("onboarding-blocker"),ia=D("onboarding-count"),Wh=D("onboarding-bar-fill"),Kh=D("live-card"),Gh=D("stats-card"),oa=D("install-steps"),Ae=D("install-err"),_e=D("btn-install"),Qh=D("menubar-banner"),aa=D("btn-menubar-restart"),No=D("btn-live"),Oo=D("btn-live-stop"),sa=D("btn-live-front"),Le=D("live-status"),Ft=D("live-img"),Ml=D("switch-personal"),ql=D("switch-promo"),ve=D("switch-pause"),Yh=D("pause-name"),Xh=D("split-row"),eg=D("split-desc"),$e=D("split-slider"),Ce=D("posting-volume-select"),tg=D("posting-volume-desc"),ng=D("mode-sub"),rg=D("settings-card"),nr=D("settings-toggle"),fe=D("settings-body");let E=null,rr=!1,Ut=!1,ir=!1,ke=!1,Yn=!1,Qe=!1,De=!1,or=!1;function F(e){Jh.textContent=e}function ig(e){switch(e){case"done":return"✓";case"running":return"…";case"error":return"×";default:return"·"}}function To(e){if(!e||!Array.isArray(e.steps)){oa.innerHTML="";return}oa.innerHTML=e.steps.map(n=>{const r=n.detail&&n.status!=="pending"?` <span class="detail">${n.status==="error"?n.detail:""}</span>`:"";return`<li class="${n.status}"><span class="glyph">${ig(n.status)}</span><span>${n.label}${r}</span></li>`}).join(""),e.error?(Ae.textContent=e.error,Ae.hidden=!1):Ae.hidden=!0}const og={environment_checked:"Environment checked",runtime_ready:"Runtime ready",x_connected:"X connected",profile_scanned:"Profile scanned",project_ready:"Project ready",topics_seeded:"Topics seeded",tasks_scheduled:"Tasks scheduled"};function ag(e){switch(e){case"complete":return"✓";case"in_progress":return"…";case"blocked":return"×";default:return"·"}}function mr(){Ll.hidden=!ke,Ne.setAttribute("aria-expanded",String(ke)),Ne.classList.toggle("expanded",ke)}function Hl(e){return e?e.setup_complete!==void 0?!!e.setup_complete:!!e.runtime_ready&&(e.projects_ready||0)>0&&!!e.x_connected:!1}function sg(e){if(!e||!Array.isArray(e.milestones)){Ne.hidden=!0,Ll.hidden=!0;return}const n=Hl(E);Ne.hidden=n,n&&ke&&(ke=!1,mr());const r=e.milestones.length,o=e.milestones.filter(i=>i.status==="complete").length,t=!!e.current_blocker&&!n;Ne.classList.toggle("complete",n),Ne.classList.toggle("blocked",t),ia.hidden=n,ia.textContent=t?`${o}/${r} · needs you`:Ut?`${o}/${r} · setting up…`:`${o}/${r}`,Wh.style.width=r>0?`${Math.round(o/r*100)}%`:"0%",Vh.innerHTML=e.milestones.map(i=>{const a=og[i.id]||i.id,s=i.attempts>1?` <span class="detail">${i.attempts} attempts</span>`:"";return`<li class="${i.status}"><span class="glyph">${ag(i.status)}</span><span>${a}${s}</span></li>`}).join(""),e.current_blocker?(tr.textContent=`Current blocker: ${e.current_blocker.message}`,tr.hidden=!1,ke=!0):tr.hidden=!0,mr()}function Jt(){if(!E)return;sg(E.onboarding),Al.innerHTML=E.update_available&&E.latest_version?`v${E.version} · <button id="btn-update" class="update-btn">Update to ${E.latest_version}</button>`:`v${E.version}`;const e=!E.runtime_ready;Bh.hidden=!e,Qh.hidden=e||E.menubar_running!==!1;const n=Hl(E);Xe.hidden=n,Xe.disabled=!1,Xe.classList.toggle("primary",!n);const r=n&&(E.schedule_state==="missing"||E.schedule_state==="disabled"||E.schedule_state==="stalled");Ht.hidden=!r,Ht.classList.toggle("primary",r);const o=E.flags||(E.mode==="promotion"?{personal_brand:!1,promotion:!0}:{personal_brand:!0,promotion:!1}),t=!!o.personal_brand,i=!!o.promotion;Ml.setAttribute("aria-checked",String(t)),ql.setAttribute("aria-checked",String(i));const a=Math.round((typeof E.personal_brand_share=="number"?E.personal_brand_share:.5)*100);Xh.hidden=!(t&&i),Yn||($e.value=String(a),Vl(a)),ng.textContent=t&&i?`Both lanes on: cycles split ${a}/${100-a} personal/promotion.`:!t&&!i?"No lane on; the cycle falls back to personal brand.":"";const s=E.paused===!0;ve.setAttribute("aria-checked",String(!s)),ve.setAttribute("aria-label",s?"Resume S4L":"Pause S4L"),ve.title=s?"Resume S4L":"Pause S4L",Yh.textContent=s?"Paused":"Running",Kh.hidden=!n,Gh.hidden=!n,rg.hidden=e}function be(e){E={...E||{},...e},Jt()}function cg(e){const n=Array.isArray(e.projects)?e.projects:[];return{projects:n,projects_total:n.length,projects_ready:n.filter(r=>r.ready).length,x_connected:!!e.x_connected,x_state:e.x_state||"",x_handle:e.x_handle??null,...e.setup_complete!==void 0?{setup_complete:!!e.setup_complete}:{},version:e.mcp_version||(E==null?void 0:E.version)||"",latest_version:e.latest_version??null,update_available:!!e.update_available,mode:e.mode??(E==null?void 0:E.mode),flags:e.flags??(E==null?void 0:E.flags),personal_brand_share:e.personal_brand_share??(E==null?void 0:E.personal_brand_share),onboarding:e.onboarding}}const Ie=Fh();function Fl(e){var n,r,o;e.theme&&Lh(e.theme),(n=e.styles)!=null&&n.variables&&Mh(e.styles.variables),(o=(r=e.styles)==null?void 0:r.css)!=null&&o.fonts&&qh(e.styles.css.fonts)}Ie.onhostcontextchanged=Fl;Ie.onerror=e=>console.error(e);Ie.ontoolresult=e=>{const n=Zl(e);n&&typeof n.projects_total=="number"&&(be(n),n.runtime_ready?Jl():n.runtime_provisioning&&Po())};async function Y(e,n={}){const r=await Ie.callServerTool({name:e,arguments:n});return Zl(r)}async function ge(){F("Refreshing…");try{const[e,n]=await Promise.all([Y("project_config",{status:!0}),Y("runtime",{action:"status"}).catch(()=>({}))]);be({...cg(e),...typeof n.runtime_ready=="boolean"?{runtime_ready:n.runtime_ready}:{},...typeof n.menubar_running=="boolean"?{menubar_running:n.menubar_running}:{},...typeof n.paused=="boolean"?{paused:n.paused}:{},onboarding:n.onboarding||e.onboarding||(E==null?void 0:E.onboarding)}),E&&!E.runtime_ready&&n.provisioning&&Po(),F(""),Jl()}catch(e){F("Refresh failed: "+((e==null?void 0:e.message)||e))}}async function Po(){if(!rr){rr=!0,_e.disabled=!0,_e.textContent="Installing…";try{for(;;){const e=await Y("runtime",{action:"status"}).catch(()=>({}));if(To(e.progress??null),e.onboarding&&be({onboarding:e.onboarding}),e.runtime_ready){be({runtime_ready:!0}),F("Runtime installed; you're ready to set up."),ge();return}const n=e.progress??null;if(n&&n.done&&!n.ok){_e.disabled=!1,_e.textContent="Retry install",F("Install failed; see the step above, then Retry.");return}await new Promise(r=>setTimeout(r,1500))}}finally{rr=!1}}}async function ug(){var r;if(Ut)return;Ut=!0,Jt();const e=Date.now(),n=1200*1e3;try{for(;;){const o=await Y("runtime",{action:"status"}).catch(()=>({}));o.progress&&To(o.progress);const t={};if(typeof o.runtime_ready=="boolean"&&(t.runtime_ready=o.runtime_ready),o.onboarding&&(t.onboarding=o.onboarding),Object.keys(t).length&&be(t),(r=o.onboarding)!=null&&r.complete){await ge(),F("Setup complete.");break}if(Date.now()-e>n)break;await new Promise(i=>setTimeout(i,2e3))}}finally{Ut=!1,Jt()}}async function Jl(){try{const e=await Y("get_stats",{days:7}),n=Array.isArray(e.projects)?e.projects[0]:null,r=n==null?void 0:n.posts;if(!r){Pt.innerHTML='<div class="muted">No stats yet.</div>';return}const o=[["Posts",r.total??0],["Active",r.active??0],["Views",r.views_period_total??r.views??0],["Replies",r.comments_period_total??r.comments??0],["Clicks",r.post_clicks_period_total??0]];Pt.innerHTML=o.map(([t,i])=>`<div class="stat"><div class="n">${i}</div><div class="l">${t}</div></div>`).join("")}catch(e){Pt.innerHTML=`<div class="muted">Stats unavailable: ${(e==null?void 0:e.message)||e}</div>`}}function Xn(e,n,r){const o=e.textContent;e.disabled=!0,e.textContent=n,r().finally(()=>{e.disabled=!1,e.textContent=o,Jt()})}Xe.addEventListener("click",()=>Xn(Xe,"Starting…",async()=>{F("Asking Claude to run setup…");try{const e=await Ie.sendMessage({role:"user",content:[{type:"text",text:"Set up S4L plugin end to end now. Inspect and repair the runtime, auto-detect and connect my X session, scan my profile, discover and research my product, then infer and save a complete project with seeded search topics. Keep going without asking me to approve each safe setup step. Ask only if I must interactively sign in or no product can be identified. Keep every reply to me extremely concise: a few short sentences at most, no step-by-step narration or long status walls. If you must ask me something (e.g. the product URL), make it one short question."}]});e!=null&&e.isError?F("The host rejected the setup request — type “set up S4L” in the chat instead."):(F("Setup is running in the chat. It will only stop for an unavoidable login or missing product."),ug())}catch(e){F("Couldn’t start setup: "+((e==null?void 0:e.message)||e))}}));Ht.addEventListener("click",()=>Xn(Ht,"Setting up…",async()=>{F("Asking Claude to schedule the draft tasks for this account…"),Y("client_event",{event:"rearm_clicked",surface:"panel"}).catch(()=>{});try{const e=await Ie.sendMessage({role:"user",content:[{type:"text",text:`Set up the S4L draft autopilot schedule for this Claude account. If queue_setup is available, call it; then for s4l-worker call the host tool create_scheduled_task with taskId, cronExpression "* * * * *", notifyOnCompletion false (REQUIRED — the default true pops a notification every minute), and the prompt — read it from ~/.claude/scheduled-tasks/s4l-worker/SKILL.md (already on disk). If the task already exists, call update_scheduled_task with taskId s4l-worker and notifyOnCompletion false instead. Before calling create_scheduled_task/list_scheduled_tasks/update_scheduled_task, run (via Bash, best-effort, ignore any error and continue if the path doesn't exist): python3 ~/.social-autoposter-mcp/repo/package/scripts/mark_event.py start <that tool's name>; immediately after it returns, run the same with 'end' instead of 'start'. Do NOT redo my X connection or project setup. Keep replies to me very short.`}]});e!=null&&e.isError?F("The host rejected it — type “set up the draft schedule” in the chat instead."):F("Scheduling is running in the chat. The draft tasks will register for this account.")}catch(e){F("Couldn’t start scheduling: "+((e==null?void 0:e.message)||e))}}));function Bl(e,n){e.addEventListener("click",async()=>{if(!e.disabled){e.disabled=!0,e.setAttribute("aria-checked",String(e.getAttribute("aria-checked")!=="true"));try{const r=await Y("engagement_mode",{action:"toggle",lane:n});r&&r.flags&&be({flags:r.flags}),await ge()}catch(r){F("Couldn’t switch lane: "+((r==null?void 0:r.message)||r)),await ge()}finally{e.disabled=!1}}})}Bl(Ml,"personal_brand");Bl(ql,"promotion");ve.addEventListener("click",async()=>{if(ve.disabled)return;const e=ve.getAttribute("aria-checked")!=="true",n=e?"resume":"pause";ve.disabled=!0,ve.setAttribute("aria-checked",String(e));try{const r=await Y("pause_s4l",{action:n});F(r!=null&&r.ok?n==="pause"?"S4L paused — drafting and posting are stopped.":"S4L resumed.":`Couldn’t ${n} S4L${r!=null&&r.detail?": "+r.detail:"."}`),await ge()}catch(r){F(`Couldn’t ${n} S4L: `+((r==null?void 0:r.message)||r)),await ge()}finally{ve.disabled=!1}});function Vl(e){eg.textContent=`${e}% personal brand / ${100-e}% promotion`}$e.addEventListener("pointerdown",()=>{Yn=!0});$e.addEventListener("input",()=>{Yn=!0,Vl(Number($e.value))});$e.addEventListener("change",async()=>{const e=Number($e.value);$e.disabled=!0;try{const n=await Y("engagement_mode",{action:"split",split:e});n&&typeof n.personal_brand_share=="number"&&be({personal_brand_share:n.personal_brand_share}),await ge()}catch(n){F("Couldn’t set the lane split: "+((n==null?void 0:n.message)||n)),await ge()}finally{$e.disabled=!1,Yn=!1}});let Wl=!1;function lg(e){if(!e||e.error)return;const n=e.mode||"medium";Ce.value=n;const r=Array.isArray(e.rates)?e.rates:[],o={high:"Aggressive",medium:"Steady",low:"Chill"};for(const t of r){const i=Ce.querySelector(`option[value="${t.mode}"]`);if(i&&t.est_posts_per_day!==null&&t.est_posts_per_day!==void 0){const a=Number(t.est_posts_per_day),s=o[t.mode]||t.mode;i.textContent=`${s} (~${a>=10?Math.round(a):a}/day)`}}tg.textContent="How many drafts per day the cycle produces."}async function pr(){try{const e=await Y("posting_volume",{action:"get"});Wl=!0,lg(e)}catch{}}Ce.addEventListener("change",async()=>{const e=Ce.value;Ce.disabled=!0;try{const n=await Y("posting_volume",{action:"set",mode:e});n&&n.error?F("Couldn’t change posting volume: "+n.error):F(`Posting volume set to ${{high:"Aggressive",medium:"Steady",low:"Chill"}[e]||e}. Applies from the next cycle.`),await pr()}catch(n){F("Couldn’t change posting volume: "+((n==null?void 0:n.message)||n)),await pr()}finally{Ce.disabled=!1}});Ne.addEventListener("click",()=>{ke=!ke,mr()});er.addEventListener("click",()=>{Qe=!Qe,Pt.hidden=!Qe,er.setAttribute("aria-expanded",String(Qe)),er.classList.toggle("expanded",Qe)});Al.addEventListener("click",e=>{const n=e.target;n&&n.id==="btn-update"&&dg()});async function dg(){if(ir)return;ir=!0;const e=document.getElementById("btn-update");e&&(e.disabled=!0,e.textContent="Updating…"),F("Installing the latest release… this can take a minute.");try{const n=await Y("runtime",{action:"update"});n.ok?(F(`Updated to ${n.latest_published||"the latest version"}. ${n.takes_effect||"Restart the client to apply."}`),e&&(e.textContent="Update installed — restart to apply")):(F("Update failed (exit "+(n.exit_code??"?")+"). Try `npx social-autoposter@latest update` in a terminal."),e&&(e.disabled=!1,e.textContent="Retry update"))}catch(n){F("Update failed: "+((n==null?void 0:n.message)||n)),e&&(e.disabled=!1,e.textContent="Retry update")}finally{ir=!1}}_e.addEventListener("click",async()=>{Ae.hidden=!0,_e.disabled=!0,_e.textContent="Starting…",F("Installing the runtime — this is a one-time download (~150MB+).");try{const e=await Y("runtime",{action:"install"});if(e.runtime_ready){be({runtime_ready:!0}),ge();return}To(e.progress??null),Po()}catch(e){_e.disabled=!1,_e.textContent="Retry install",Ae.textContent="Couldn't start install: "+((e==null?void 0:e.message)||e),Ae.hidden=!1}});aa.addEventListener("click",()=>Xn(aa,"Restarting…",async()=>{F("Restarting the S4L menu bar…");try{const e=await Y("restart_menubar");typeof e.menubar_running=="boolean"&&be({menubar_running:e.menubar_running}),F(e.menubar_running?"Menu bar restarted.":"Couldn’t confirm the menu bar came back"+(e.detail?": "+e.detail:"."))}catch(e){F("Couldn’t restart the menu bar: "+((e==null?void 0:e.message)||e))}}));const mg=["website","description","icp","voice","differentiator","search_topics","get_started_link","content_guardrails"],pg=["description","voice","search_topics","content_angle","content_guardrails"],fr={website:"Website",description:"What it does",icp:"Target audience",voice:"Voice",differentiator:"Differentiator",search_topics:"Search topics",get_started_link:"Get-started link",content_guardrails:"Content guardrails",content_angle:"Content angle"},ca=new Set(["website","description","icp","voice","differentiator","search_topics","get_started_link","content_guardrails"]),fg=new Set(["website","get_started_link"]);function et(e){const n=e.replace(/[_-]+/g," ").trim();return n?n.charAt(0).toUpperCase()+n.slice(1):e}function Kl(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function ua(e){return Array.isArray(e)&&e.every(n=>typeof n=="string")?{kind:"list",text:e.join(`
|
|
74
74
|
`)}:e==null||typeof e=="string"?{kind:"text",text:String(e??"")}:{kind:"json",text:JSON.stringify(e,null,2)}}function la(e,n,r){const o=document.createElement("div");o.className="settings-field";const t=document.createElement("label");t.textContent=e+(n.kind==="list"?" (one per line)":""),o.appendChild(t);let i;if(n.kind==="text"&&r)i=document.createElement("input"),i.type="text",i.className="settings-input";else{const a=document.createElement("textarea"),s=n.text?n.text.split(`
|
|
75
75
|
`).reduce((c,p)=>c+Math.max(1,Math.ceil(p.length/60)),0):1;a.rows=Math.min(10,Math.max(2,s)),a.className="settings-textarea"+(n.kind==="json"?" mono":""),i=a}return i.value=n.text,n.text||(i.placeholder="Not set"),i.dataset.orig=n.text,o.appendChild(i),{wrap:o,el:i}}function hg(e){const n=document.createElement("div");n.className="settings-project";const r=document.createElement("div");r.className="settings-project-head";const o=document.createElement("span");if(o.className="settings-project-name",o.textContent=e.name,r.appendChild(o),e.persona){const f=document.createElement("span");f.className="settings-project-tag",f.textContent="personal brand",r.appendChild(f)}const t=document.createElement("span");t.className="settings-project-state",t.textContent=e.ready?"ready":"missing: "+e.missing_required.join(", "),r.appendChild(t),n.appendChild(r);const i=[],a=e.persona?pg:mg;for(const f of a){const _=e.fields[f];if(Kl(_)&&Object.keys(_).length){const z=document.createElement("fieldset");z.className="settings-group";const S=document.createElement("legend");S.textContent=fr[f]||et(f),z.appendChild(S);for(const[g,$]of Object.entries(_)){const y=ua($),I=la(et(g),y,!1);z.appendChild(I.wrap),i.push({key:f,sub:g,kind:y.kind,el:I.el,orig:y.text})}n.appendChild(z);continue}const w=f==="search_topics"?{kind:"list",text:Array.isArray(_)?_.map(String).join(`
|
|
76
76
|
`):String(_??"")}:ua(_),b=la(fr[f]||et(f),w,fg.has(f));n.appendChild(b.wrap),i.push({key:f,kind:w.kind,el:b.el,orig:w.text})}if(e.extra_keys.length){const f=document.createElement("div");f.className="settings-extra",f.textContent="Advanced (edit via chat): "+e.extra_keys.join(", "),n.appendChild(f)}const s=document.createElement("div");s.className="settings-actions";const c=document.createElement("button");c.className="primary",c.textContent="Save changes",c.disabled=!0;const p=document.createElement("span");p.className="settings-status",s.appendChild(c),s.appendChild(p),n.appendChild(s);const h=()=>{c.disabled=!i.some(f=>f.el.value!==f.orig)};for(const f of i)f.el.addEventListener("input",h);return c.addEventListener("click",()=>void gg(e,i,c,p)),n}async function gg(e,n,r,o){const t=n.filter(h=>h.el.value!==h.orig);if(!t.length)return;const i={name:e.name},a={},s=h=>(fr[h.key]||et(h.key))+(h.sub?` · ${et(h.sub)}`:""),c=[...new Set(t.filter(h=>h.sub!==void 0).map(h=>h.key))];for(const h of c){const f=Kl(e.fields[h])?{...e.fields[h]}:{};for(const _ of n.filter(w=>w.key===h&&w.sub!==void 0)){const w=_.el.value;if(_.kind==="list"){const b=w.split(`
|
|
@@ -190,9 +190,8 @@ container holding the app. Specify either width or maxWidth, and either height o
|
|
|
190
190
|
<div class="lane-desc" id="posting-volume-desc">How many drafts per day the cycle produces.</div>
|
|
191
191
|
</div>
|
|
192
192
|
<select id="posting-volume-select" aria-label="Posting volume">
|
|
193
|
-
<option value="">Default</option>
|
|
194
193
|
<option value="high">Aggressive</option>
|
|
195
|
-
<option value="medium">Steady</option>
|
|
194
|
+
<option value="medium" selected>Steady</option>
|
|
196
195
|
<option value="low">Chill</option>
|
|
197
196
|
</select>
|
|
198
197
|
</div>
|
package/mcp/dist/version.json
CHANGED
package/mcp/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dxt_version": "0.1",
|
|
3
3
|
"name": "social-autoposter",
|
|
4
4
|
"display_name": "S4L",
|
|
5
|
-
"version": "1.7.5-rc.
|
|
5
|
+
"version": "1.7.5-rc.9",
|
|
6
6
|
"description": "Draft, review, approve, and autopilot X/Twitter posts.",
|
|
7
7
|
"long_description": "## **⚠️ The disclaimer above is generic Claude boilerplate.** Anthropic shows the same warning on every plugin regardless of what it does; any plugin has the same level of access as any app you download from the internet.\n\nS4L is an open source product developed by Mediar.ai Incorporated, a VC-backed San Francisco-based startup.\n\nTo get started:\n\n1\\. Copy this prompt: **Set me up on S4L plugin end to end**\n\n2\\. Quit with CMD+Q, reopen Claude, paste into a new chat.\n\nWhat happens next:\n\n* About every 5 minutes S4L scans X for posts that match your topics and drafts replies in your voice.\n* Drafts show up as review cards, usually the first within a few minutes. Nothing is posted automatically; you approve each one.\n* Posting autopilot stays off until you explicitly turn it on.",
|
|
8
8
|
"author": {
|
|
@@ -1151,13 +1151,14 @@ class S4LMenuBar(rumps.App):
|
|
|
1151
1151
|
)
|
|
1152
1152
|
|
|
1153
1153
|
# Posting volume (2026-07-13): server-side per-install throttle for the
|
|
1154
|
-
# twitter cycle's virality bar.
|
|
1155
|
-
#
|
|
1156
|
-
#
|
|
1157
|
-
#
|
|
1158
|
-
# (
|
|
1154
|
+
# twitter cycle's virality bar. The labels carry ballpark rates; exact
|
|
1155
|
+
# per-install estimates live on the dashboard's Posting Volume section
|
|
1156
|
+
# (Stats tab). Internal values stay high|medium|low (DB CHECK + API enum);
|
|
1157
|
+
# Aggressive/Steady/Chill are display names only (renamed 2026-07-14).
|
|
1158
|
+
# There is NO separate "default" state (removed 2026-07-14 per user): an
|
|
1159
|
+
# install that never chose resolves to Steady server-side, so the menu
|
|
1160
|
+
# shows Steady checked.
|
|
1159
1161
|
POSTING_MODE_PRESETS = (
|
|
1160
|
-
(None, "Default (cycle setting)"),
|
|
1161
1162
|
("high", "Aggressive (100+ posts/day)"),
|
|
1162
1163
|
("medium", "Steady (~30 posts/day)"),
|
|
1163
1164
|
("low", "Chill (~5 posts/day)"),
|
|
@@ -1188,11 +1189,11 @@ class S4LMenuBar(rumps.App):
|
|
|
1188
1189
|
sys.stderr.flush()
|
|
1189
1190
|
|
|
1190
1191
|
def _posting_mode_label(self, mode):
|
|
1191
|
-
"""Menu copy for a posting-volume mode value."""
|
|
1192
|
+
"""Menu copy for a posting-volume mode value (unknown/unset = Steady)."""
|
|
1192
1193
|
for preset, label in self.POSTING_MODE_PRESETS:
|
|
1193
1194
|
if preset == mode:
|
|
1194
1195
|
return label
|
|
1195
|
-
return "
|
|
1196
|
+
return "Steady (~30 posts/day)"
|
|
1196
1197
|
|
|
1197
1198
|
def _on_posting_mode_preset(self, mode, _sender=None):
|
|
1198
1199
|
"""Set the server-side posting-volume mode (network POST; a click may
|
|
@@ -2213,6 +2214,14 @@ class S4LMenuBar(rumps.App):
|
|
|
2213
2214
|
):
|
|
2214
2215
|
label = self._posting_label
|
|
2215
2216
|
if label:
|
|
2217
|
+
# Menu bar stays CONCISE (user rule 2026-07-14): strip the trailing
|
|
2218
|
+
# elapsed-duration token the producer encodes in the label ("draft
|
|
2219
|
+
# 2m", "draft ⧖18m") from the TITLE only. The full label (with the
|
|
2220
|
+
# duration the stall detector's _label_elapsed_secs parses) still
|
|
2221
|
+
# lives in activity.json — this is display-side trimming, never a
|
|
2222
|
+
# change to the data.
|
|
2223
|
+
import re as _re
|
|
2224
|
+
label = _re.sub(r"\s*⧖?\s*\d+\s*[sm]\s*$", "", str(label)).strip() or label
|
|
2216
2225
|
# The update arrow must stay visible even while a tool runs, so the
|
|
2217
2226
|
# "update available" signal is never masked by activity. _tick skips the
|
|
2218
2227
|
# title repaint while the spinner owns it, so the arrow is injected here.
|
|
@@ -3571,10 +3580,10 @@ class S4LMenuBar(rumps.App):
|
|
|
3571
3580
|
items.append(cadence_menu)
|
|
3572
3581
|
|
|
3573
3582
|
# Posting volume: server-side per-install throttle (virality bar).
|
|
3574
|
-
# read_posting_mode() is cached and non-blocking;
|
|
3575
|
-
#
|
|
3576
|
-
# the
|
|
3577
|
-
pv_mode = st.read_posting_mode()
|
|
3583
|
+
# read_posting_mode() is cached and non-blocking; unknown/unset shows
|
|
3584
|
+
# as Steady (the server resolves unset the same way), corrected on the
|
|
3585
|
+
# rebuild after the first refresh lands.
|
|
3586
|
+
pv_mode = st.read_posting_mode() or "medium"
|
|
3578
3587
|
pv_menu = rumps.MenuItem(
|
|
3579
3588
|
f"Posting volume: {self._posting_mode_label(pv_mode).split(' (')[0].lower()}"
|
|
3580
3589
|
)
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/s4l-mcp",
|
|
3
|
-
"version": "1.7.5-rc.
|
|
3
|
+
"version": "1.7.5-rc.9",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
|
|
6
6
|
"license": "MIT",
|
package/package.json
CHANGED
package/scripts/claude_job.py
CHANGED
|
@@ -88,7 +88,10 @@ TYPE_TO_ACTIVITY = {
|
|
|
88
88
|
"invent-topic": ("learning", "new topic"),
|
|
89
89
|
"invent-queries": ("learning", "new queries"),
|
|
90
90
|
"twitter-link-tail": ("drafting", "link bridge"),
|
|
91
|
-
"
|
|
91
|
+
# Same bare "draft" label as twitter-prep ON PURPOSE: the menu bar title
|
|
92
|
+
# must stay concise (user rule 2026-07-14); the platform shows on the
|
|
93
|
+
# review card itself, not in the tray.
|
|
94
|
+
"reddit-draft": ("drafting", "draft"),
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
# queue type -> execution notes PREPENDED to the prompt sidecar at claim time.
|
package/scripts/stats.py
CHANGED
|
@@ -441,6 +441,26 @@ def fetch_reddit_json(url, user_agent, max_retries=2, timeout=15):
|
|
|
441
441
|
(success AND error) into _reddit_rate_state so the caller can pace.
|
|
442
442
|
On 429, honors Retry-After (capped to 120s) and retries.
|
|
443
443
|
"""
|
|
444
|
+
# 2026-07-14 transport fix: Reddit started 403-blocking plain urllib on
|
|
445
|
+
# *.json (2026-05-28), which silently killed this scan (every poll came
|
|
446
|
+
# back as an HTML block page -> 'empty'/'error', zero rows patched since).
|
|
447
|
+
# Route through the logged-in harness browser first, the same transport
|
|
448
|
+
# reddit_tools._do_request has used since 2026-05-29 (the replies pass in
|
|
449
|
+
# this file already goes through it via batch_fetch_info and kept working).
|
|
450
|
+
# urllib below stays as the fallback; REDDIT_FETCH_BACKEND=urllib forces it.
|
|
451
|
+
if os.environ.get("REDDIT_FETCH_BACKEND", "harness").lower() != "urllib":
|
|
452
|
+
try:
|
|
453
|
+
from reddit_browser_fetch import browser_get_json
|
|
454
|
+
body, code = browser_get_json(url)
|
|
455
|
+
if code == 200 and body:
|
|
456
|
+
try:
|
|
457
|
+
return ("ok", json.loads(body))
|
|
458
|
+
except Exception:
|
|
459
|
+
pass # non-JSON body -> fall through to urllib
|
|
460
|
+
elif code == 404:
|
|
461
|
+
return ("not_found", None)
|
|
462
|
+
except Exception:
|
|
463
|
+
pass
|
|
444
464
|
req = urllib.request.Request(url, headers={"User-Agent": user_agent})
|
|
445
465
|
for attempt in range(max_retries + 1):
|
|
446
466
|
try:
|