@forsion/extend 0.9.4 → 0.9.6
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/CHANGELOG.md +24 -0
- package/README.md +14 -0
- package/SIGNATURE +10 -8
- package/dist/desktop.mjs +46 -35
- package/dist/main.js +224 -50
- package/manifest.json +2 -2
- package/package.json +3 -1
- package/tangu-plugins/store-update/dist/index.mjs +2 -0
- package/tangu-plugins/store-update/tangu-plugin.json +8 -0
package/manifest.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"id": "forsion-extend",
|
|
3
3
|
"name": "Forsion 扩展",
|
|
4
4
|
"nameEn": "Forsion Extend",
|
|
5
|
-
"version": "0.9.
|
|
5
|
+
"version": "0.9.6",
|
|
6
6
|
"apiVersion": 1,
|
|
7
|
-
"minAppVersion": "2.13.
|
|
7
|
+
"minAppVersion": "2.13.8-beta.3",
|
|
8
8
|
"main": "dist/main.js",
|
|
9
9
|
"description": "Forsion 云端账号在桌面端的功能:登录与切换账号,设置里的「Forsion 云端」(账号、个人主页、额度与积分、背包、插件投稿、安全、用量记录、反馈),Forsion 公告,笔记云同步与分享,设备互联的云端中转与设备列表,Forsion Connect 发布,以及「使用指南」Space(按等级解锁的说明书,带任务与快捷跳转)。随 Forsion 桌面端内置,自动更新。",
|
|
10
10
|
"descriptionEn": "Your Forsion cloud account on the desktop: signing in and switching accounts, the Forsion Cloud pages in Settings (account, profile, quota & points, backpack, submissions, security, usage, feedback), Forsion announcements, note cloud sync and sharing, the cloud relay and device list for connecting your devices, Forsion Connect publishing, and the Guide Space (a manual that unlocks level by level, with tasks and shortcuts). Built into Forsion Desktop and updated automatically.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forsion/extend",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "Forsion Extend: the cloud-account half of Forsion Desktop plus Forsion Connect (Coding Space publishing and the preview-mode AI proxy), shipped as a built-in bundle (signed, updated through npm).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"dist/main.js",
|
|
20
20
|
"dist/desktop.mjs",
|
|
21
21
|
"dist/THIRD_PARTY_NOTICES.md",
|
|
22
|
+
"tangu-plugins/store-update/tangu-plugin.json",
|
|
23
|
+
"tangu-plugins/store-update/dist/index.mjs",
|
|
22
24
|
"spaces/guide/space.json",
|
|
23
25
|
"spaces/penzor/space.json",
|
|
24
26
|
"spaces/penzor/icon.svg",
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var V=["name","summary","description","tags","minAppVersion","maxAppVersion","platforms"],M=e=>JSON.stringify(Object.keys(e).sort().map(t=>[t,e[t]])),H='Error: mode "info" needs at least one of newName, summary, description, tags, minAppVersion, maxAppVersion, platforms.';var Q=/^[a-zA-Z0-9_-]{1,128}$/;function L(e=Date.now){let t=new Map,n=s=>e()-s.at<=18e5;return{set(s){for(let[a,o]of t)n(o)||t.delete(a);let r=globalThis.crypto.randomUUID().slice(0,8);return t.set(r,{...s,id:r}),r},get(s){let r=t.get(s);return r?n(r)?r:(t.delete(s),null):null},delete(s){t.delete(s)}}}var h=e=>typeof e=="string"?e.trim():"",v=e=>h(e).replace(/^v(?=\d)/i,""),F=e=>e==="info"?"info":"package";var p=e=>JSON.stringify(e);function I(e){let t=typeof e.json?.error=="string"?e.json.error:"",n=e.json?.values&&typeof e.json.values=="object"?e.json.values:{};if(e.status===401||e.error==="not_signed_in")return"Not signed in to Forsion. Ask the user to sign in under Settings \u2192 Forsion Cloud, then try again.";switch(t){case"market_update_pending":return`An earlier update${n.version?` (v${n.version})`:""} is still in review. Wait for the result (it arrives in the inbox) before submitting another.`;case"market_version_not_newer":return`Upstream${n.version?` ${n.version}`:""} is not newer than the Store's${n.current?` ${n.current}`:""}. Publish a newer GitHub release or npm version first.`;case"market_no_changes":return"Nothing changed: the given store info equals what the Store already has.";case"market_no_release":return"The GitHub repository has no release yet. Publish a GitHub release first.";case"market_npm_invalid":return"The npm package has no usable version.";case"market_version_moved":return`The latest version moved to ${n.version||"a newer one"}${n.tag?` (${n.tag})`:""} since approval; nothing was submitted. Run store_check_update again and confirm with the user.`;case"market_stale":return"The item changed meanwhile (a review or another update). Run store_check_update again.";case"market_not_found":return"That item was not found among your submissions.";case"market_requirements_invalid":case"market_requirements_mismatch":return`The version requirements were refused: ${h(e.json?.detail)||t}.`}if(e.status===0)return`Could not reach Forsion: ${e.error||"network error"}.`;let s=h(e.json?.detail);return`The Store refused the request (HTTP ${e.status}${t?`, ${t}`:""})${s?`: ${s}`:""}.`}var X=e=>e.status===0||e.status>=500;async function T(e,t){try{let n=await e.request({timeoutMs:3e4,...t});return n&&typeof n.status=="number"?n:{status:0,error:"empty response"}}catch(n){return{status:0,error:String(n?.message||n)}}}async function j(e,t){let n=await T(e,{path:"/api/market/mine",method:"GET",signal:t});if(n.status<200||n.status>=300)return{ok:!1,error:I(n)};let s=Array.isArray(n.json?.items)?n.json.items.filter(r=>r&&typeof r.id=="string"&&typeof r.name=="string"):[];return n.json?.dryRun!==!0?{ok:!1,error:"This Forsion server cannot check updates before submitting. Update the item from the Submissions page instead."}:{ok:!0,items:s,expectedVersion:n.json?.expectedVersion===!0}}var y=e=>e.trim().toLocaleLowerCase();function ee(e,t){let n=t.trim(),s=e.find(o=>o.id===n);if(s)return{item:s};let r=e.filter(o=>y(o.name)===y(n));return r.length===1?{item:r[0]}:r.length>1?{candidates:r}:{none:!0,similar:n?e.filter(o=>y(o.name).includes(y(n))||y(n).includes(y(o.name))):[]}}var R=e=>({itemId:e.id,name:e.name,type:e.type,source:e.source,version:e.version??null,status:e.status}),P=(e,t)=>t==="name"?e.newName:e[t];function N(e){let t={};for(let n of V){let s=P(e,n);if(s!=null)if(n==="tags"||n==="platforms")if(typeof s=="string")t[n]=s.split(/[,,\s]+/).map(r=>r.trim()).filter(Boolean);else if(Array.isArray(s)&&s.every(r=>typeof r=="string"))t[n]=s.map(r=>r.trim()).filter(Boolean);else return{body:t,bad:n};else if(typeof s=="string")t[n]=s;else return{body:t,bad:n==="name"?"newName":n}}return{body:t}}var A=(e,t)=>`/api/market/mine/${encodeURIComponent(e)}/update${t?"?dryRun=1":""}`;function O(e,t){let n={};t?.name&&t.name!==e.name&&(n.name={from:e.name,to:t.name}),typeof t?.summary=="string"&&t.summary!==(e.summary||"")&&(n.summary={from:e.summary||"",to:t.summary});let s=t?.metadata&&typeof t.metadata=="object"?t.metadata:{};for(let r of["description","tags","minAppVersion","maxAppVersion","platforms"]){if(!(r in s))continue;let a=e[r]??(r==="tags"||r==="platforms"?[]:r==="description"?"":null);p(a)!==p(s[r])&&(n[r]={from:a,to:s[r]})}return n}var D=e=>e.length>240?`${e.slice(0,240)}\u2026`:e,te=e=>Object.fromEntries(Object.entries(e).map(([t,n])=>[t,t==="description"?{from:D(String(n.from??"")),to:D(String(n.to??""))}:n])),G=e=>Object.fromEntries(Object.entries(e).map(([t,n])=>[t,n.to]));async function ne(e,t,n,s){let r=await j(e,s);if(!r.ok)return{reply:`Error: ${r.error}`};if(!r.items.length)return{reply:p({ok:!1,reason:"no_submissions",message:"This account has no Store submissions. A first listing goes through the Submissions page."})};let a=ee(r.items,t);if("candidates"in a)return{reply:p({ok:!1,reason:"ambiguous",message:"Several of your submissions have this name. Ask the user which one, then pass its itemId.",candidates:a.candidates.map(R)})};if("none"in a)return{reply:p({ok:!1,reason:"not_found",message:`None of your submissions is called "${t}". Only your own submissions can be updated.`,...a.similar.length?{similar:a.similar.map(R)}:{yourItems:r.items.slice(0,20).map(R)}})};let o=a.item;return n==="package"&&o.source==="zip"?{reply:p({ok:!1,reason:"zip_source",itemId:o.id,name:o.name,message:'This item is a ZIP upload. Tangu cannot upload packages yet: update it from the Submissions page (Settings \u2192 Forsion Cloud \u2192 Submissions). Store info can still be changed here with mode "info".'})}:o.pendingUpdate?{reply:p({ok:!1,reason:"update_pending",itemId:o.id,name:o.name,pending:{kind:o.pendingUpdate.kind||"package",version:o.pendingUpdate.version??null},message:"An earlier update of this item is still in review. Wait for the result (it arrives in the inbox) before submitting another."})}:{item:o}}async function z(e,t,n,s,r=Date.now){let a=h(n.item)||h(n.itemId);if(!a)return'Error: pass "item" (the item name or id).';let o=F(n.mode),c=o==="info"?N(n):{body:{}};if(c.bad)return`Error: "${c.bad}" has the wrong type.`;if(o==="info"&&!Object.keys(c.body).length)return H;let m=await ne(e,a,o,s);if("reply"in m)return m.reply;let i=m.item,u=await T(e,{path:A(i.id,!0),method:"POST",body:{mode:o,...c.body},signal:s}),b={itemId:i.id,name:i.name,type:i.type,source:i.source,status:i.status,mode:o};if(u.status<200||u.status>=300){let l=u.json?.error;return l==="market_version_not_newer"||l==="market_no_release"||l==="market_no_changes"||l==="market_update_pending"?p({ok:!1,reason:l.replace(/^market_/,""),...b,current:u.json?.values?.current??i.version??null,...u.json?.values?.version?{latest:u.json.values.version}:{},message:I(u)}):`Error: ${I(u)}`}let d=u.json||{},S=i.status==="approved",k=[];S||k.push(i.status==="rejected"?"This item is not listed (it was rejected). Submitting sends it back to the review queue.":"This item is not listed yet. Submitting sends it back to the review queue.");let f="To submit, call store_submit_update with this checkId and these values. The user approves it on an approval card; it then goes to review.";if(o==="package"){let l=v(d.version);if(!l)return"Error: The check did not report a version.";let _=d.current??i.version??null,C=t.set({itemId:i.id,name:i.name,type:i.type,source:i.source,mode:o,current:_,version:l,tag:d.tag??null,at:r()});return p({ok:!0,canSubmit:!0,checkId:C,...b,current:_,version:l,tag:d.tag??null,...k.length?{warnings:k}:{},next:f})}let g=O(i,d);if(!Object.keys(g).length)return p({ok:!1,reason:"no_changes",...b,message:"Nothing changed: the given store info equals what the Store already has."});let $=t.set({itemId:i.id,name:i.name,type:i.type,source:i.source,mode:o,current:i.version??null,fields:Object.keys(g),changes:G(g),body:M(c.body),at:r()});return p({ok:!0,canSubmit:!0,checkId:$,...b,current:i.version??null,changes:te(g),...k.length?{warnings:k}:{},next:f})}var re={"amadeus-plugin":"plugin",plugin:"engine plugin",skill:"skill",agent:"agent",space:"space",theme:"theme"},Z=(e,t,n,s)=>{let r=h(t.checkId)?e.get(h(t.checkId)):null;return r&&r.itemId===n&&r.mode===s?r:null};function B(e,t){let n=F(t.mode),s=h(t.itemId),r=Z(e,t,s,n),o=[`Submit a Store update: ${r?.name||h(t.name)||s||"an item"}${r?.type?` (${re[r.type]||r.type})`:""}`],c=r?r.current:null;if(n==="package"){let m=r?.version||v(t.version),i=c?`In the Store ${c} \u2192 `:"",u=r?.source==="github"?` (GitHub release ${r.tag||`v${m}`})`:r?.source==="npm"?` (npm ${m})`:"";o.push(`${i}new version ${m||"unknown"}${u}`),r?v(t.version)!==r.version&&o.push(`The last check found ${r.version}, not ${v(t.version)||"no version"}: this submission will be refused.`):o.push("No matching check: this submission will be refused until store_check_update runs.")}else{let m=r?.fields?.length?r.fields:V.filter(i=>P(t,i)!==void 0&&P(t,i)!==null);o.push(`Store info changes: ${m.join(", ")||"none"}`);for(let[i,u]of Object.entries(r?.changes||{}))o.push(` ${i}: ${typeof u=="string"?u:p(u)}`);r?M(N(t).body)!==r.body&&o.push("These fields differ from the last check: this submission will be refused."):o.push("No matching check: this submission will be refused until store_check_update runs.")}return o.push(`It goes to review; the Store keeps ${c||"the current version"} until it is approved.`),o.join(`
|
|
2
|
+
`)}async function J(e,t,n,s,r=Date.now){let a=h(n.itemId),o=h(n.name),c=F(n.mode);if(!Q.test(a))return'Error: pass "itemId" exactly as store_check_update reported it.';if(!o)return'Error: pass "name" exactly as store_check_update reported it.';let m=v(n.version);if(c==="package"&&!m)return'Error: pass "version" exactly as store_check_update reported it.';let i=c==="info"?N(n):{body:{}};if(i.bad)return`Error: "${i.bad}" has the wrong type.`;if(c==="info"&&!Object.keys(i.body).length)return H;let u=Z(t,n,a,c);if(!u)return"Error: Run store_check_update for this item first (checks expire after 30 minutes), then submit with the checkId and values it reported.";if(c==="package"&&m!==u.version)return`Error: The last check found ${u.version}, not ${m}. Submit the checked version, or run store_check_update again and confirm with the user.`;if(c==="info"&&M(i.body)!==u.body)return"Error: These store info fields differ from the last check. Run store_check_update again with the same fields and confirm with the user.";let b=await j(e,s);if(!b.ok)return`Error: ${b.error}`;let d=b.items.find(w=>w.id===a);if(!d)return"Error: That itemId is not one of your submissions. Run store_check_update again.";if(y(d.name)!==y(o))return`Error: Item ${a} is called "${d.name}", not "${o}". Run store_check_update again and confirm with the user.`;if(c==="package"&&d.source==="zip")return"Error: ZIP-source items are updated from the Submissions page (Settings \u2192 Forsion Cloud \u2192 Submissions).";if(d.pendingUpdate)return"Error: An earlier update of this item is still in review. Wait for the result before submitting another.";let S={mode:c,...i.body},k=await T(e,{path:A(a,!0),method:"POST",body:S,signal:s});if(k.status<200||k.status>=300)return`Error: ${I(k)}`;let f=k.json||{},g=c==="package"?v(f.version):"";if(c==="package"){if(g!==m||(u.tag??null)!==(f.tag??null))return t.delete(u.id),`Error: The latest version is now ${g||"unknown"}${f.tag?` (${f.tag})`:""}, not ${m}${u.tag?` (${u.tag})`:""}. Run store_check_update again and confirm with the user.`}else{let w=O(d,f);if(!Object.keys(w).length)return"Error: Nothing changed: the given store info equals what the Store already has.";if(p(G(w))!==p(u.changes||{}))return t.delete(u.id),"Error: The Store info changed since the check, so this update would now change different fields than the user approved. Run store_check_update again and confirm with the user."}s?.throwIfAborted?.();let $=c==="package"&&b.expectedVersion?{expectedVersion:g,expectedTag:f.tag??null}:{},l=await T(e,{path:A(a,!1),method:"POST",body:{...S,...$},signal:s});if(l.status<200||l.status>=300)return l.json?.error==="market_version_moved"&&t.delete(u.id),X(l)?(t.delete(u.id),`Error: Unknown outcome: the Store did not answer clearly (${l.status?`HTTP ${l.status}`:l.error||"no response"}), so this update may or may not have been submitted. Do not submit again: run store_check_update first (it reports an update in review), then tell the user.`):`Error: ${I(l)}`;t.delete(u.id);let _=await j(e,s).catch(()=>null),C=_?.ok?_.items.find(w=>w.id===a)?.pendingUpdate:null,E=C?.submittedAt,Y=typeof E=="string"&&Number.isFinite(Date.parse(E))?E:new Date(r()).toISOString(),q=(c==="package"?f.current:null)??d.version??null,x=c==="package"?v(C?.version):"";return x&&x!==g?`Error: Submitted, but the Store staged ${x} instead of the approved ${g} (a new upstream release landed meanwhile). It is in review now; tell the user, who can wait for the review or ask an admin to reject it.`:p({itemId:a,name:d.name,type:d.type,source:d.source,mode:c,from:q,to:c==="package"?g:d.version??null,tag:c==="package"?f.tag??null:null,...c==="info"?{fields:Object.keys(O(d,f))}:{},listed:d.status==="approved",status:"in_review",submittedAt:Y,message:`Submitted for review. The Store keeps ${q??"the current version"} until it is approved; the review result arrives in the inbox.`})}var U="store-update",W={newName:{type:"string",description:"Info mode: new display name."},summary:{type:"string",description:"Info mode: new one-line summary."},description:{type:"string",description:"Info mode: new long description (Markdown)."},tags:{type:"array",items:{type:"string"},description:"Info mode: tags (replaces the list; up to 8)."},minAppVersion:{type:"string",description:"Info mode: minimum Forsion version, such as 2.13.0. Empty string clears it."},maxAppVersion:{type:"string",description:"Info mode: maximum Forsion version (exclusive). Empty string clears it."},platforms:{type:"array",items:{type:"string",enum:["desktop","macos","windows","linux","android","ios","web"]},description:"Info mode: supported platforms (replaces the list; empty = all)."}};function se(e,t,n=Date.now){let s=L(n),r=()=>e.sdk.pluginStore.isPluginEnabledSync(U);return[{name:"store_check_update",mode:"both",isEnabledFor:r,capabilities:{sideEffect:"network",parallel:!1,defaultTimeoutMs:6e4},definition:{type:"function",function:{name:"store_check_update",description:`Check whether one of the signed-in user's own Forsion Store submissions can be updated, without changing anything. mode "package" (default): reads the newest GitHub release / npm version the Store would pick up and compares it with the listed version. mode "info": previews a change of store info (name, summary, description, tags, version requirements, platforms). Returns JSON. ok:false with a reason (ambiguous, not_found, zip_source, update_pending, version_not_newer, no_release, no_changes) means stop and tell the user; for "ambiguous" ask which item and pass its itemId. Always run this before store_submit_update. ZIP-uploaded packages cannot be updated here.`,parameters:{type:"object",properties:{item:{type:"string",description:"Item name (case-insensitive) or item id."},mode:{type:"string",enum:["package","info"],description:"Default package."},...W},required:["item"]}}},execute:(a,o)=>z(t,s,a,o?.signal,n)},{name:"store_submit_update",mode:"both",isEnabledFor:r,capabilities:{sideEffect:"network",parallel:!1,approval:"always",approvalPreview:a=>B(s,a||{}),defaultTimeoutMs:15e4},definition:{type:"function",function:{name:"store_submit_update",description:"Submit a Forsion Store update for review, for one of the signed-in user's own submissions. Only after store_check_update returned ok:true for the same item and mode, and only when the user asked for it: pass checkId, itemId, name and version exactly as that check reported them (info mode: the same info fields). The user approves every call on an approval card. The tool checks again first and refuses if anything moved; then run store_check_update again. Listed items keep their current version in the Store until the update is approved; the review result arrives in the user's inbox.",parameters:{type:"object",properties:{checkId:{type:"string",description:"checkId from the store_check_update result this submission is based on."},itemId:{type:"string",description:"Item id from store_check_update."},name:{type:"string",description:"Item name from store_check_update."},mode:{type:"string",enum:["package","info"],description:"Default package."},version:{type:"string",description:"Package mode: the new version store_check_update reported."},...W},required:["checkId","itemId","name"]}}},execute:(a,o)=>J(t,s,a,o?.signal,n)}]}var K=!1,oe={activate(e){let t=e.cloud;if(!t||typeof t.request!="function"){K||e.log("store-update: this engine gives the plugin no cloud access (needs an officially signed bundle on a newer engine); tools not registered"),K=!0;return}let n=se(e,t);e.registerPlugin({id:U,name:"\u5546\u5E97\u66F4\u65B0",nameEn:"Store updates",description:"\u8BA9 Tangu \u66FF\u4F60\u63D0\u4EA4\u81EA\u5DF1\u5728 Forsion \u5546\u5E97\u7684\u6295\u7A3F\u66F4\u65B0\uFF08GitHub / npm \u65B0\u7248\u672C\u6216\u5546\u5E97\u4FE1\u606F\uFF09\uFF0C\u6BCF\u6B21\u90FD\u8981\u4F60\u6279\u51C6\u3002",descriptionEn:"Lets Tangu submit updates to your own Forsion Store submissions (a new GitHub / npm version or store info). You approve every submission.",scopes:["global"],defaultEnabled:!0,settings:{fields:[]},toolProvider:{id:`plugin:${U}`,tools:()=>n}}),e.log("store-update activated")},deactivate(){}},ue=oe;export{U as PLUGIN_ID,se as buildTools,ue as default};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "store-update",
|
|
3
|
+
"name": "商店更新",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"apiVersion": 1,
|
|
6
|
+
"entry": "dist/index.mjs",
|
|
7
|
+
"description": "Lets Tangu submit updates to the signed-in author's own Forsion Store submissions (store_check_update / store_submit_update). Built from src/tangu/storeUpdate by scripts/build.mjs."
|
|
8
|
+
}
|