koishi-plugin-new-auth 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/index.js +1 -0
- package/dist/style.css +1 -0
- package/lib/index.d.ts +123 -1
- package/lib/index.js +275 -4
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ It implements the first usable version described in `newauth.md`:
|
|
|
10
10
|
- Koishi's legacy `authority` value is recorded as a suggestion, not used as the final grant;
|
|
11
11
|
- policies are evaluated by `scope + role + command`;
|
|
12
12
|
- guild owner/admin/member roles are separated from Bot administrator;
|
|
13
|
-
- custom roles and role members can be managed from Koishi commands
|
|
13
|
+
- custom roles and role members can be managed from Koishi commands;
|
|
14
|
+
- Koishi Console WebUI is available when `@koishijs/plugin-console` is installed.
|
|
14
15
|
|
|
15
16
|
## Configuration
|
|
16
17
|
|
|
@@ -67,3 +68,18 @@ guest
|
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
Custom roles do not inherit from other roles. To give a custom role access to a command, add an explicit policy with `newauth.allow`.
|
|
71
|
+
|
|
72
|
+
## WebUI
|
|
73
|
+
|
|
74
|
+
Install Koishi Console and open the `⌗ 新权限` page.
|
|
75
|
+
|
|
76
|
+
The page provides:
|
|
77
|
+
|
|
78
|
+
- role-first command policy editing;
|
|
79
|
+
- global and guild scope switching;
|
|
80
|
+
- pending command review;
|
|
81
|
+
- legacy `authority` suggestions;
|
|
82
|
+
- AI-style automatic assignment for pending commands;
|
|
83
|
+
- command status and guild override toggles.
|
|
84
|
+
|
|
85
|
+
The automatic assignment advisor is conservative: dangerous instance-level commands are kept for `bot-admin`, group moderation commands go to `guild-admin` and `guild-owner`, normal user commands go to `guild-member` and above, and unclear commands fall back to the legacy authority suggestion.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineComponent as H,ref as v,computed as c,watch as J,openBlock as o,createElementBlock as a,createElementVNode as t,toDisplayString as s,withDirectives as y,Fragment as m,renderList as x,vModelSelect as U,normalizeClass as g,createCommentVNode as G,vModelCheckbox as K,createTextVNode as T,vModelText as D}from"vue";import{store as E,send as k}from"@koishijs/client";const Q={class:"new-auth-page"},W={class:"topbar"},X={class:"top-actions"},Y=["value"],Z=["disabled"],ee=["disabled"],te={class:"tabs"},le={key:0,class:"error"},ne={key:1,class:"role-layout"},se={class:"role-list"},oe=["onClick"],ae={key:0,class:"detail"},ie={class:"detail-head"},ue={class:"check"},de={class:"filters"},re={class:"command-list"},ce={class:"command-main"},ve={class:"state-buttons"},pe=["onClick"],ge=["onClick"],he=["onClick"],_e={key:2,class:"pending-grid"},fe={class:"row-actions"},be=["disabled","onClick"],ye=["disabled","onClick"],ke={key:0,class:"empty"},Ce={key:3,class:"commands-table"},we={class:"filters"},me={class:"command-main"},xe={class:"command-controls"},Ae={class:"check"},Ie=["checked","onChange"],Ve=["value","onChange"],$e=H({__name:"new-auth",setup(C){const i=v("roles"),u=v("global"),d=v(""),p=v(""),h=v("all"),$=v(false),_=v(false),A=v(""),w=c(()=>E.newauth||{roles:[],commands:[],policies:[],scopes:[{id:"global",name:"全局默认"}],pendingCount:0}),I=c(()=>w.value.roles),P=c(()=>w.value.commands),L=c(()=>w.value.policies),B=c(()=>{var n;return(n=w.value.scopes)!=null&&n.length?w.value.scopes:[{id:"global",name:"全局默认"}]}),V=c(()=>P.value.filter(n=>n.status==="pending")),f=c(()=>I.value.find(n=>n.id===d.value));J(I,n=>{!d.value&&n.length&&(d.value=n[0].id)},{immediate:true});const M=c(()=>{const n=p.value.toLowerCase();return P.value.filter(l=>h.value!=="all"&&l.status!==h.value?false:n?[l.id,l.name,l.commandPath,l.plugin,l.description].some(e=>e==null?void 0:e.toLowerCase().includes(n)):true)}),F=c(()=>f.value?M.value.filter(n=>{var l,e;return $.value||((l=f.value)==null?void 0:l.id)==="bot-admin"||((e=f.value)==null?void 0:e.scopeType)==="global"?true:n.allowGuildOverride}):[]);function S(n){const l=L.value.find(e=>e.scope===u.value&&e.roleId===d.value&&e.commandId===n);return(l==null?void 0:l.state)||"inherit"}async function b(n){_.value=true,A.value="";try{await n}catch(l){A.value=l instanceof Error?l.message:String(l)}finally{_.value=false}}async function R(){return b((async()=>{const n=await k("newauth/getData");E.newauth=n})())}function O(n,l){return b(k("newauth/setPolicy",{scope:u.value,roleId:d.value,commandId:n,state:l}))}function N(n,l){return b(k("newauth/applySuggestion",{commandId:n,scope:u.value,mode:l}))}function q(){return b(k("newauth/autoAssignPending",{scope:u.value,mode:"advisor"}))}function z(n,l){const e=l.target;return b(k("newauth/setGuildOverride",{commandId:n.id,allowGuildOverride:e.checked}))}function j(n,l){const e=l.target;return b(k("newauth/setCommandStatus",{commandId:n.id,status:e.value}))}return(n,l)=>(o(),a("main",Q,[t("header",W,[t("div",null,[l[9]||(l[9]=t("h1",null,"新权限",-1)),t("p",null,s(I.value.length)+" 个角色 / "+s(P.value.length)+" 个指令 / "+s(V.value.length)+" 个待配置",1)]),t("div",X,[y(t("select",{"onUpdate:modelValue":l[0]||(l[0]=e=>u.value=e),class:"scope-select"},[(o(true),a(m,null,x(B.value,e=>(o(),a("option",{key:e.id,value:e.id},s(e.name),9,Y))),128))],512),[[U,u.value]]),t("button",{class:"primary",disabled:_.value||!V.value.length,onClick:q}," AI 自动分配待配置 ",8,Z),t("button",{disabled:_.value,onClick:R},"刷新",8,ee)])]),t("nav",te,[t("button",{class:g({active:i.value==="roles"}),onClick:l[1]||(l[1]=e=>i.value="roles")},"角色",2),t("button",{class:g({active:i.value==="pending"}),onClick:l[2]||(l[2]=e=>i.value="pending")},"待配置",2),t("button",{class:g({active:i.value==="commands"}),onClick:l[3]||(l[3]=e=>i.value="commands")},"指令",2)]),A.value?(o(),a("p",le,s(A.value),1)):G("v-if",true),i.value==="roles"?(o(),a("section",ne,[t("aside",se,[(o(true),a(m,null,x(I.value,e=>(o(),a("button",{key:e.id,class:g(["role-item",{active:e.id===d.value}]),onClick:r=>d.value=e.id},[t("span",null,[t("strong",null,s(e.name),1),t("small",null,s(e.builtin?"内置角色":"自定义角色")+" / "+s(e.scopeType),1)]),t("em",null,s(e.allowCount),1)],10,oe))),128))]),f.value?(o(),a("section",ae,[t("div",ie,[t("div",null,[t("h2",null,s(f.value.name),1),t("p",null,s(f.value.id),1)]),t("label",ue,[y(t("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=e=>$.value=e)},null,512),[[K,$.value]]),l[10]||(l[10]=T(" 审计模式 ",-1))])]),t("div",de,[y(t("input",{"onUpdate:modelValue":l[5]||(l[5]=e=>p.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[D,p.value,void 0,{trim:true}]]),y(t("select",{"onUpdate:modelValue":l[6]||(l[6]=e=>h.value=e)},[...l[11]||(l[11]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[U,h.value]])]),t("div",re,[(o(true),a(m,null,x(F.value,e=>(o(),a("article",{key:e.id,class:"command-row"},[t("div",ce,[t("strong",null,s(e.name),1),t("span",null,s(e.commandPath),1),t("small",null,s(e.plugin)+" / legacy "+s(e.legacyAuthority)+" / "+s(e.suggestion.label),1)]),t("div",ve,[t("button",{class:g({active:S(e.id)==="inherit"}),onClick:r=>O(e.id,"inherit")}," 继承 ",10,pe),t("button",{class:g({active:S(e.id)==="allow"}),onClick:r=>O(e.id,"allow")}," 开 ",10,ge),t("button",{class:g({active:S(e.id)==="deny"}),onClick:r=>O(e.id,"deny")}," 关 ",10,he)])]))),128))])])):G("v-if",true)])):i.value==="pending"?(o(),a("section",_e,[(o(true),a(m,null,x(V.value,e=>(o(),a("article",{key:e.id,class:"pending-item"},[t("div",null,[t("strong",null,s(e.name),1),t("span",null,s(e.commandPath),1),t("small",null,s(e.plugin)+" / legacy "+s(e.legacyAuthority),1)]),t("dl",null,[l[12]||(l[12]=t("dt",null,"旧建议",-1)),t("dd",null,s(e.suggestion.label),1),l[13]||(l[13]=t("dt",null,"AI 建议",-1)),t("dd",null,s(e.autoAssign.label)+":"+s(e.autoAssign.reason),1)]),t("div",fe,[t("button",{disabled:_.value,onClick:r=>N(e.id,"legacy")},"采用旧建议",8,be),t("button",{class:"primary",disabled:_.value,onClick:r=>N(e.id,"advisor")},"采用 AI 建议",8,ye)])]))),128)),V.value.length?G("v-if",true):(o(),a("p",ke,"没有待配置指令。"))])):(o(),a("section",Ce,[t("div",we,[y(t("input",{"onUpdate:modelValue":l[7]||(l[7]=e=>p.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[D,p.value,void 0,{trim:true}]]),y(t("select",{"onUpdate:modelValue":l[8]||(l[8]=e=>h.value=e)},[...l[14]||(l[14]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[U,h.value]])]),(o(true),a(m,null,x(M.value,e=>(o(),a("article",{key:e.id,class:"command-row"},[t("div",me,[t("strong",null,s(e.name),1),t("span",null,s(e.id),1),t("small",null,s(e.plugin)+" / "+s(e.commandPath)+" / "+s(e.autoAssign.label),1)]),t("div",xe,[t("label",Ae,[t("input",{type:"checkbox",checked:e.allowGuildOverride,onChange:r=>z(e,r)},null,40,Ie),l[15]||(l[15]=T(" 群内自治 ",-1))]),t("select",{value:e.status,onChange:r=>j(e,r)},[...l[16]||(l[16]=[t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],40,Ve)])]))),128))]))]))}}),Pe=(C,i)=>{const u=C.__vccOpts||C;for(const[d,p]of i)u[d]=p;return u},Se=Pe($e,[["__scopeId","data-v-b49c6f52"]]),Ge=C=>{C.page({name:"新权限",path:"/new-auth",icon:"⌗",fields:["newauth"],authority:4,component:Se})};export{Ge as default};
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.new-auth-page[data-v-b49c6f52]{min-height:100vh;background:#f6f7f4;color:#1f2523;padding:20px}.topbar[data-v-b49c6f52],.detail-head[data-v-b49c6f52],.filters[data-v-b49c6f52],.row-actions[data-v-b49c6f52],.command-controls[data-v-b49c6f52],.top-actions[data-v-b49c6f52]{display:flex;align-items:center;gap:12px}.topbar[data-v-b49c6f52]{justify-content:space-between;border-bottom:1px solid #d9ded6;padding-bottom:16px}h1[data-v-b49c6f52],h2[data-v-b49c6f52],p[data-v-b49c6f52]{margin:0}h1[data-v-b49c6f52]{font-size:26px;line-height:1.2}h2[data-v-b49c6f52]{font-size:20px}p[data-v-b49c6f52],small[data-v-b49c6f52],span[data-v-b49c6f52],dd[data-v-b49c6f52],dt[data-v-b49c6f52]{color:#63706a}button[data-v-b49c6f52],select[data-v-b49c6f52],input[data-v-b49c6f52]{border:1px solid #c7cec8;border-radius:6px;background:#fff;color:#1f2523;min-height:34px;padding:0 10px;font:inherit}button[data-v-b49c6f52]{cursor:pointer}button[data-v-b49c6f52]:disabled{cursor:not-allowed;opacity:.55}.primary[data-v-b49c6f52]{background:#285e54;border-color:#285e54;color:#fff}.tabs[data-v-b49c6f52]{display:flex;gap:6px;margin:16px 0}.tabs button.active[data-v-b49c6f52],.state-buttons button.active[data-v-b49c6f52]{background:#25312d;border-color:#25312d;color:#fff}.role-layout[data-v-b49c6f52]{display:grid;grid-template-columns:minmax(220px,280px) 1fr;gap:16px}.role-list[data-v-b49c6f52]{display:grid;gap:8px;align-content:start}.role-item[data-v-b49c6f52]{display:flex;justify-content:space-between;align-items:center;min-height:58px;text-align:left}.role-item span[data-v-b49c6f52],.command-main[data-v-b49c6f52]{display:grid;gap:3px;min-width:0}.role-item.active[data-v-b49c6f52]{border-color:#285e54;box-shadow:inset 3px 0 #285e54}.role-item em[data-v-b49c6f52]{font-style:normal;color:#8b4a2f}.detail[data-v-b49c6f52]{min-width:0}.detail-head[data-v-b49c6f52]{justify-content:space-between;margin-bottom:12px}.filters[data-v-b49c6f52]{margin-bottom:12px}.filters input[data-v-b49c6f52]{flex:1;min-width:180px}.command-list[data-v-b49c6f52],.pending-grid[data-v-b49c6f52],.commands-table[data-v-b49c6f52]{display:grid;gap:8px}.command-row[data-v-b49c6f52],.pending-item[data-v-b49c6f52]{background:#fff;border:1px solid #d9ded6;border-radius:8px;padding:12px}.command-row[data-v-b49c6f52]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center}.command-main strong[data-v-b49c6f52],.command-main span[data-v-b49c6f52],.command-main small[data-v-b49c6f52]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.state-buttons[data-v-b49c6f52]{display:grid;grid-template-columns:repeat(3,54px);gap:6px}.pending-grid[data-v-b49c6f52]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}.pending-item[data-v-b49c6f52]{display:grid;gap:10px}dl[data-v-b49c6f52]{display:grid;grid-template-columns:58px 1fr;gap:4px 8px;margin:0}dd[data-v-b49c6f52]{margin:0}.check[data-v-b49c6f52]{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.check input[data-v-b49c6f52]{min-height:0}.error[data-v-b49c6f52]{border:1px solid #d9a09a;background:#fff1ef;color:#8a2d22;border-radius:6px;padding:10px;margin-bottom:12px}.empty[data-v-b49c6f52]{padding:24px 0}@media (max-width: 760px){.new-auth-page[data-v-b49c6f52]{padding:12px}.topbar[data-v-b49c6f52],.detail-head[data-v-b49c6f52],.command-row[data-v-b49c6f52]{grid-template-columns:1fr;display:grid}.top-actions[data-v-b49c6f52],.filters[data-v-b49c6f52],.command-controls[data-v-b49c6f52]{flex-wrap:wrap}.role-layout[data-v-b49c6f52]{grid-template-columns:1fr}.state-buttons[data-v-b49c6f52]{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Command, Context, Schema, Session } from 'koishi';
|
|
2
2
|
import type { Argv } from 'koishi';
|
|
3
3
|
export declare const name = "new-auth";
|
|
4
|
-
export declare const inject:
|
|
4
|
+
export declare const inject: {
|
|
5
|
+
required: string[];
|
|
6
|
+
optional: string[];
|
|
7
|
+
};
|
|
5
8
|
type RoleType = 'builtin' | 'custom';
|
|
6
9
|
type ScopeType = 'global' | 'guild';
|
|
7
10
|
type CommandStatus = 'pending' | 'configured' | 'disabled';
|
|
8
11
|
type PolicyState = 'inherit' | 'allow' | 'deny';
|
|
12
|
+
type AutoAssignKind = 'public' | 'moderation' | 'owner' | 'admin' | 'legacy';
|
|
9
13
|
export interface NewAuthCommandRecord {
|
|
10
14
|
id: string;
|
|
11
15
|
name: string;
|
|
@@ -53,6 +57,112 @@ declare module 'koishi' {
|
|
|
53
57
|
newauth: NewAuthService;
|
|
54
58
|
}
|
|
55
59
|
}
|
|
60
|
+
declare module '@koishijs/plugin-console' {
|
|
61
|
+
namespace Console {
|
|
62
|
+
interface Services {
|
|
63
|
+
newauth: NewAuthConsoleService;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
interface Events {
|
|
67
|
+
'newauth/getData'(): Promise<NewAuthConsoleData>;
|
|
68
|
+
'newauth/setPolicy'(payload: SetPolicyPayload): Promise<{
|
|
69
|
+
success: true;
|
|
70
|
+
}>;
|
|
71
|
+
'newauth/setCommandStatus'(payload: SetCommandStatusPayload): Promise<{
|
|
72
|
+
success: true;
|
|
73
|
+
}>;
|
|
74
|
+
'newauth/setGuildOverride'(payload: SetGuildOverridePayload): Promise<{
|
|
75
|
+
success: true;
|
|
76
|
+
}>;
|
|
77
|
+
'newauth/applySuggestion'(payload: ApplySuggestionPayload): Promise<{
|
|
78
|
+
success: true;
|
|
79
|
+
}>;
|
|
80
|
+
'newauth/autoAssignPending'(payload?: AutoAssignPayload): Promise<{
|
|
81
|
+
success: true;
|
|
82
|
+
count: number;
|
|
83
|
+
}>;
|
|
84
|
+
'newauth/createRole'(payload: CreateRolePayload): Promise<{
|
|
85
|
+
success: true;
|
|
86
|
+
}>;
|
|
87
|
+
'newauth/addMember'(payload: MemberPayload): Promise<{
|
|
88
|
+
success: true;
|
|
89
|
+
}>;
|
|
90
|
+
'newauth/removeMember'(payload: MemberPayload): Promise<{
|
|
91
|
+
success: true;
|
|
92
|
+
}>;
|
|
93
|
+
'newauth/copyRolePolicies'(payload: CopyRolePoliciesPayload): Promise<{
|
|
94
|
+
success: true;
|
|
95
|
+
count: number;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export interface NewAuthConsoleData {
|
|
100
|
+
roles: RoleView[];
|
|
101
|
+
commands: CommandView[];
|
|
102
|
+
policies: NewAuthPolicyRecord[];
|
|
103
|
+
members: NewAuthRoleMemberRecord[];
|
|
104
|
+
scopes: ScopeView[];
|
|
105
|
+
pendingCount: number;
|
|
106
|
+
autoAssignAvailable: boolean;
|
|
107
|
+
}
|
|
108
|
+
export interface RoleView extends NewAuthRoleRecord {
|
|
109
|
+
allowCount: number;
|
|
110
|
+
}
|
|
111
|
+
export interface CommandView extends NewAuthCommandRecord {
|
|
112
|
+
suggestion: RoleSuggestion;
|
|
113
|
+
autoAssign: AutoAssignSuggestion;
|
|
114
|
+
}
|
|
115
|
+
export interface ScopeView {
|
|
116
|
+
id: string;
|
|
117
|
+
name: string;
|
|
118
|
+
type: ScopeType;
|
|
119
|
+
}
|
|
120
|
+
export interface RoleSuggestion {
|
|
121
|
+
roles: string[];
|
|
122
|
+
label: string;
|
|
123
|
+
}
|
|
124
|
+
export interface AutoAssignSuggestion extends RoleSuggestion {
|
|
125
|
+
kind: AutoAssignKind;
|
|
126
|
+
reason: string;
|
|
127
|
+
}
|
|
128
|
+
interface SetPolicyPayload {
|
|
129
|
+
scope: string;
|
|
130
|
+
roleId: string;
|
|
131
|
+
commandId: string;
|
|
132
|
+
state: PolicyState;
|
|
133
|
+
}
|
|
134
|
+
interface SetCommandStatusPayload {
|
|
135
|
+
commandId: string;
|
|
136
|
+
status: CommandStatus;
|
|
137
|
+
}
|
|
138
|
+
interface SetGuildOverridePayload {
|
|
139
|
+
commandId: string;
|
|
140
|
+
allowGuildOverride: boolean;
|
|
141
|
+
}
|
|
142
|
+
interface ApplySuggestionPayload {
|
|
143
|
+
commandId: string;
|
|
144
|
+
scope?: string;
|
|
145
|
+
mode?: 'legacy' | 'advisor';
|
|
146
|
+
}
|
|
147
|
+
interface AutoAssignPayload {
|
|
148
|
+
scope?: string;
|
|
149
|
+
mode?: 'legacy' | 'advisor';
|
|
150
|
+
}
|
|
151
|
+
interface CreateRolePayload {
|
|
152
|
+
id: string;
|
|
153
|
+
name: string;
|
|
154
|
+
scopeType: ScopeType;
|
|
155
|
+
}
|
|
156
|
+
interface MemberPayload {
|
|
157
|
+
roleId: string;
|
|
158
|
+
uid: string;
|
|
159
|
+
scope: string;
|
|
160
|
+
}
|
|
161
|
+
interface CopyRolePoliciesPayload {
|
|
162
|
+
sourceRoleId: string;
|
|
163
|
+
targetRoleId: string;
|
|
164
|
+
scope: string;
|
|
165
|
+
}
|
|
56
166
|
export interface Config {
|
|
57
167
|
botAdmins: string[];
|
|
58
168
|
trustLegacyAuthorityAsAdmin: boolean;
|
|
@@ -128,24 +238,36 @@ export declare class NewAuthService {
|
|
|
128
238
|
query?: string;
|
|
129
239
|
}): Promise<NewAuthCommandRecord[]>;
|
|
130
240
|
listRoles(): Promise<NewAuthRoleRecord[]>;
|
|
241
|
+
listPolicies(): Promise<NewAuthPolicyRecord[]>;
|
|
242
|
+
listMembers(): Promise<NewAuthRoleMemberRecord[]>;
|
|
243
|
+
listScopes(): Promise<ScopeView[]>;
|
|
244
|
+
getConsoleData(): Promise<NewAuthConsoleData>;
|
|
131
245
|
addBotAdmin(uid: string): Promise<void>;
|
|
132
246
|
removeBotAdmin(uid: string): Promise<void>;
|
|
133
247
|
createCustomRole(id: string, name: string, scopeType?: ScopeType): Promise<void>;
|
|
134
248
|
addRoleMember(roleId: string, uid: string, scope?: string): Promise<void>;
|
|
135
249
|
removeRoleMember(roleId: string, uid: string, scope?: string): Promise<void>;
|
|
250
|
+
copyRolePolicies(sourceRoleId: string, targetRoleId: string, scope?: string): Promise<number>;
|
|
136
251
|
setCommandStatus(input: string, status: CommandStatus): Promise<NewAuthCommandRecord>;
|
|
252
|
+
setCommandGuildOverride(input: string, allowGuildOverride: boolean): Promise<NewAuthCommandRecord>;
|
|
137
253
|
setCommandPolicy(scope: string, roleId: string, input: string, state: PolicyState): Promise<NewAuthCommandRecord>;
|
|
254
|
+
applySuggestedPolicy(input: string, scope?: string, mode?: 'legacy' | 'advisor'): Promise<NewAuthCommandRecord>;
|
|
255
|
+
autoAssignPending(scope?: string, mode?: 'legacy' | 'advisor'): Promise<number>;
|
|
138
256
|
getCommand(input: string): Promise<NewAuthCommandRecord>;
|
|
139
257
|
private ensureBuiltinRoles;
|
|
258
|
+
private applyRolesToCommand;
|
|
140
259
|
private createCommandRecord;
|
|
141
260
|
private getDescription;
|
|
142
261
|
private resolveCommandInput;
|
|
143
262
|
private setPolicy;
|
|
144
263
|
private getEffectivePolicy;
|
|
264
|
+
private getAutoAssignSuggestion;
|
|
145
265
|
private ensureRoleMember;
|
|
266
|
+
private ensureRoleExists;
|
|
146
267
|
private hasPlatformRole;
|
|
147
268
|
private grantRuntimeCommandPermission;
|
|
148
269
|
private getCommandList;
|
|
149
270
|
private isSelfCommand;
|
|
150
271
|
}
|
|
272
|
+
export type NewAuthConsoleService = any;
|
|
151
273
|
export {};
|
package/lib/index.js
CHANGED
|
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.NewAuthService = exports.Config = exports.inject = exports.name = void 0;
|
|
4
4
|
exports.apply = apply;
|
|
5
5
|
const koishi_1 = require("koishi");
|
|
6
|
+
const path_1 = require("path");
|
|
6
7
|
exports.name = 'new-auth';
|
|
7
|
-
exports.inject = ['database'];
|
|
8
|
+
exports.inject = { required: ['database'], optional: ['console'] };
|
|
8
9
|
const BUILTIN_ROLES = [
|
|
9
10
|
{
|
|
10
11
|
id: 'bot-admin',
|
|
@@ -125,6 +126,9 @@ function apply(ctx, config) {
|
|
|
125
126
|
return service.intercept(argv);
|
|
126
127
|
});
|
|
127
128
|
registerManagementCommands(ctx, config, service);
|
|
129
|
+
ctx.inject(['console'], (ctx) => {
|
|
130
|
+
registerConsole(ctx, service);
|
|
131
|
+
});
|
|
128
132
|
}
|
|
129
133
|
class NewAuthService {
|
|
130
134
|
constructor(ctx, config) {
|
|
@@ -290,6 +294,62 @@ class NewAuthService {
|
|
|
290
294
|
const records = await this.ctx.database.get('new_auth_role', {});
|
|
291
295
|
return records.sort((a, b) => Number(a.builtin) - Number(b.builtin) || a.id.localeCompare(b.id));
|
|
292
296
|
}
|
|
297
|
+
async listPolicies() {
|
|
298
|
+
return this.ctx.database.get('new_auth_policy', {});
|
|
299
|
+
}
|
|
300
|
+
async listMembers() {
|
|
301
|
+
return this.ctx.database.get('new_auth_role_member', {});
|
|
302
|
+
}
|
|
303
|
+
async listScopes() {
|
|
304
|
+
const scopes = [{ id: 'global', name: '全局默认', type: 'global' }];
|
|
305
|
+
const seen = new Set(['global']);
|
|
306
|
+
const channels = await this.ctx.database.get('channel', {}, ['id', 'platform', 'guildId']);
|
|
307
|
+
for (const channel of channels) {
|
|
308
|
+
const guildId = channel.guildId || channel.id;
|
|
309
|
+
if (!channel.platform || !guildId)
|
|
310
|
+
continue;
|
|
311
|
+
const id = `guild:${channel.platform}:${guildId}`;
|
|
312
|
+
if (seen.has(id))
|
|
313
|
+
continue;
|
|
314
|
+
seen.add(id);
|
|
315
|
+
scopes.push({
|
|
316
|
+
id,
|
|
317
|
+
name: `${channel.platform}:${guildId}`,
|
|
318
|
+
type: 'guild',
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return scopes;
|
|
322
|
+
}
|
|
323
|
+
async getConsoleData() {
|
|
324
|
+
const [roles, commands, policies, members, scopes] = await Promise.all([
|
|
325
|
+
this.listRoles(),
|
|
326
|
+
this.listCommands({ all: true }),
|
|
327
|
+
this.listPolicies(),
|
|
328
|
+
this.listMembers(),
|
|
329
|
+
this.listScopes(),
|
|
330
|
+
]);
|
|
331
|
+
const globalAllow = new Set(policies
|
|
332
|
+
.filter(policy => policy.scope === 'global' && policy.state === 'allow')
|
|
333
|
+
.map(policy => `${policy.roleId}:${policy.commandId}`));
|
|
334
|
+
return {
|
|
335
|
+
roles: roles.map(role => ({
|
|
336
|
+
...role,
|
|
337
|
+
allowCount: role.id === 'bot-admin'
|
|
338
|
+
? commands.filter(command => command.status !== 'disabled').length
|
|
339
|
+
: commands.filter(command => globalAllow.has(`${role.id}:${command.id}`)).length,
|
|
340
|
+
})),
|
|
341
|
+
commands: commands.map(command => ({
|
|
342
|
+
...command,
|
|
343
|
+
suggestion: getLegacySuggestion(command.legacyAuthority),
|
|
344
|
+
autoAssign: this.getAutoAssignSuggestion(command),
|
|
345
|
+
})),
|
|
346
|
+
policies,
|
|
347
|
+
members,
|
|
348
|
+
scopes,
|
|
349
|
+
pendingCount: commands.filter(command => command.status === 'pending').length,
|
|
350
|
+
autoAssignAvailable: true,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
293
353
|
async addBotAdmin(uid) {
|
|
294
354
|
const parsed = parseUid(uid);
|
|
295
355
|
await this.ensureRoleMember('bot-admin', parsed.platform, parsed.userId, 'global');
|
|
@@ -338,6 +398,18 @@ class NewAuthService {
|
|
|
338
398
|
scope,
|
|
339
399
|
});
|
|
340
400
|
}
|
|
401
|
+
async copyRolePolicies(sourceRoleId, targetRoleId, scope = 'global') {
|
|
402
|
+
await this.ensureRoleExists(sourceRoleId);
|
|
403
|
+
await this.ensureRoleExists(targetRoleId);
|
|
404
|
+
const policies = await this.ctx.database.get('new_auth_policy', {
|
|
405
|
+
scope,
|
|
406
|
+
roleId: sourceRoleId,
|
|
407
|
+
});
|
|
408
|
+
for (const policy of policies) {
|
|
409
|
+
await this.setPolicy(scope, targetRoleId, policy.commandId, policy.state);
|
|
410
|
+
}
|
|
411
|
+
return policies.length;
|
|
412
|
+
}
|
|
341
413
|
async setCommandStatus(input, status) {
|
|
342
414
|
const command = await this.resolveCommandInput(input);
|
|
343
415
|
await this.ctx.database.set('new_auth_command', command.id, {
|
|
@@ -347,6 +419,15 @@ class NewAuthService {
|
|
|
347
419
|
this.commandCache.delete(command.id);
|
|
348
420
|
return command;
|
|
349
421
|
}
|
|
422
|
+
async setCommandGuildOverride(input, allowGuildOverride) {
|
|
423
|
+
const command = await this.resolveCommandInput(input);
|
|
424
|
+
await this.ctx.database.set('new_auth_command', command.id, {
|
|
425
|
+
allowGuildOverride,
|
|
426
|
+
updatedAt: new Date(),
|
|
427
|
+
});
|
|
428
|
+
this.commandCache.delete(command.id);
|
|
429
|
+
return command;
|
|
430
|
+
}
|
|
350
431
|
async setCommandPolicy(scope, roleId, input, state) {
|
|
351
432
|
const command = await this.resolveCommandInput(input);
|
|
352
433
|
await this.setPolicy(scope, roleId, command.id, state);
|
|
@@ -355,6 +436,24 @@ class NewAuthService {
|
|
|
355
436
|
}
|
|
356
437
|
return command;
|
|
357
438
|
}
|
|
439
|
+
async applySuggestedPolicy(input, scope = 'global', mode = 'legacy') {
|
|
440
|
+
const command = await this.resolveCommandInput(input);
|
|
441
|
+
const suggestion = mode === 'advisor'
|
|
442
|
+
? this.getAutoAssignSuggestion(command)
|
|
443
|
+
: getLegacySuggestion(command.legacyAuthority);
|
|
444
|
+
await this.applyRolesToCommand(scope, command.id, suggestion.roles);
|
|
445
|
+
if (command.status === 'pending') {
|
|
446
|
+
await this.setCommandStatus(command.id, 'configured');
|
|
447
|
+
}
|
|
448
|
+
return command;
|
|
449
|
+
}
|
|
450
|
+
async autoAssignPending(scope = 'global', mode = 'advisor') {
|
|
451
|
+
const commands = await this.listCommands({ pending: true });
|
|
452
|
+
for (const command of commands) {
|
|
453
|
+
await this.applySuggestedPolicy(command.id, scope, mode);
|
|
454
|
+
}
|
|
455
|
+
return commands.length;
|
|
456
|
+
}
|
|
358
457
|
async getCommand(input) {
|
|
359
458
|
const cached = this.commandCache.get(input);
|
|
360
459
|
if (cached)
|
|
@@ -382,6 +481,15 @@ class NewAuthService {
|
|
|
382
481
|
}
|
|
383
482
|
}
|
|
384
483
|
}
|
|
484
|
+
async applyRolesToCommand(scope, commandId, roleIds) {
|
|
485
|
+
const roles = await this.listRoles();
|
|
486
|
+
const knownRoles = new Set(roles.map(role => role.id));
|
|
487
|
+
for (const roleId of roleIds) {
|
|
488
|
+
if (!knownRoles.has(roleId))
|
|
489
|
+
continue;
|
|
490
|
+
await this.setPolicy(scope, roleId, commandId, 'allow');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
385
493
|
createCommandRecord(command, now) {
|
|
386
494
|
const plugin = inferPlugin(command);
|
|
387
495
|
const legacyAuthority = inferLegacyAuthority(command);
|
|
@@ -444,16 +552,84 @@ class NewAuthService {
|
|
|
444
552
|
});
|
|
445
553
|
return global?.state ?? 'inherit';
|
|
446
554
|
}
|
|
555
|
+
getAutoAssignSuggestion(command) {
|
|
556
|
+
const text = normalizeKey([
|
|
557
|
+
command.commandPath,
|
|
558
|
+
command.name,
|
|
559
|
+
command.plugin,
|
|
560
|
+
command.description,
|
|
561
|
+
command.aliases.join(' '),
|
|
562
|
+
].join(' '));
|
|
563
|
+
const dangerous = [
|
|
564
|
+
'plugin', 'market', 'install', 'uninstall', 'reload', 'restart',
|
|
565
|
+
'config', 'database', 'db', 'file', 'delete', 'remove', 'exec',
|
|
566
|
+
'eval', 'shell', 'token', 'env', 'broadcast',
|
|
567
|
+
'插件', '市场', '安装', '卸载', '重载', '重启', '配置', '数据库',
|
|
568
|
+
'文件', '删除', '执行', '脚本', '令牌', '环境变量', '广播',
|
|
569
|
+
];
|
|
570
|
+
if (dangerous.some(keyword => text.includes(keyword))) {
|
|
571
|
+
return {
|
|
572
|
+
kind: 'admin',
|
|
573
|
+
roles: ['bot-admin'],
|
|
574
|
+
label: '仅 Bot 管理员',
|
|
575
|
+
reason: '命中实例级或高危操作关键词。',
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
const moderation = [
|
|
579
|
+
'ban', 'mute', 'kick', 'warn', 'recall', 'approve', 'blacklist',
|
|
580
|
+
'禁言', '踢', '封禁', '警告', '撤回', '审核', '黑名单',
|
|
581
|
+
];
|
|
582
|
+
if (moderation.some(keyword => text.includes(keyword))) {
|
|
583
|
+
return {
|
|
584
|
+
kind: 'moderation',
|
|
585
|
+
roles: ['guild-admin', 'guild-owner'],
|
|
586
|
+
label: '群管理员和群主',
|
|
587
|
+
reason: '命中群管理操作关键词。',
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const ownerOnly = ['welcome', 'setting', 'notice', 'announce', '欢迎', '公告', '群设置'];
|
|
591
|
+
if (ownerOnly.some(keyword => text.includes(keyword))) {
|
|
592
|
+
return {
|
|
593
|
+
kind: 'owner',
|
|
594
|
+
roles: ['guild-owner'],
|
|
595
|
+
label: '群主',
|
|
596
|
+
reason: '更适合群主进行群内自治配置。',
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const publicUse = [
|
|
600
|
+
'help', 'sign', 'rank', 'weather', 'search', 'music', 'play', 'query',
|
|
601
|
+
'帮助', '签到', '排行', '天气', '搜索', '点歌', '播放', '查询',
|
|
602
|
+
];
|
|
603
|
+
if (publicUse.some(keyword => text.includes(keyword))) {
|
|
604
|
+
return {
|
|
605
|
+
kind: 'public',
|
|
606
|
+
roles: ['guild-member', 'guild-admin', 'guild-owner'],
|
|
607
|
+
label: '群成员及以上',
|
|
608
|
+
reason: '命中普通用户功能关键词。',
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const fallback = getLegacySuggestion(command.legacyAuthority);
|
|
612
|
+
return {
|
|
613
|
+
kind: 'legacy',
|
|
614
|
+
roles: fallback.roles,
|
|
615
|
+
label: fallback.label,
|
|
616
|
+
reason: '未命中明确语义,回退到旧 authority 建议。',
|
|
617
|
+
};
|
|
618
|
+
}
|
|
447
619
|
async ensureRoleMember(roleId, platform, userId, scope) {
|
|
448
|
-
|
|
449
|
-
if (!role)
|
|
450
|
-
throw new Error(`role not found: ${roleId}`);
|
|
620
|
+
await this.ensureRoleExists(roleId);
|
|
451
621
|
const query = { roleId, platform, userId, scope };
|
|
452
622
|
const [existing] = await this.ctx.database.get('new_auth_role_member', query);
|
|
453
623
|
if (!existing) {
|
|
454
624
|
await this.ctx.database.create('new_auth_role_member', { ...query, createdAt: new Date() });
|
|
455
625
|
}
|
|
456
626
|
}
|
|
627
|
+
async ensureRoleExists(roleId) {
|
|
628
|
+
const [role] = await this.ctx.database.get('new_auth_role', { id: roleId });
|
|
629
|
+
if (!role)
|
|
630
|
+
throw new Error(`role not found: ${roleId}`);
|
|
631
|
+
return role;
|
|
632
|
+
}
|
|
457
633
|
hasPlatformRole(session, roleNames) {
|
|
458
634
|
const values = new Set();
|
|
459
635
|
const author = session.author;
|
|
@@ -486,6 +662,66 @@ class NewAuthService {
|
|
|
486
662
|
}
|
|
487
663
|
}
|
|
488
664
|
exports.NewAuthService = NewAuthService;
|
|
665
|
+
function createConsoleServiceClass() {
|
|
666
|
+
const { DataService } = require('@koishijs/plugin-console');
|
|
667
|
+
return class NewAuthConsoleDataService extends DataService {
|
|
668
|
+
constructor(ctx) {
|
|
669
|
+
super(ctx, 'newauth', {
|
|
670
|
+
immediate: true,
|
|
671
|
+
authority: 4,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
async get(_forced, _client) {
|
|
675
|
+
return this.ctx.newauth.getConsoleData();
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function registerConsole(ctx, service) {
|
|
680
|
+
ctx.console.addEntry({
|
|
681
|
+
dev: (0, path_1.resolve)(__dirname, '../client/index.ts'),
|
|
682
|
+
prod: (0, path_1.resolve)(__dirname, '../dist'),
|
|
683
|
+
});
|
|
684
|
+
ctx.plugin(createConsoleServiceClass());
|
|
685
|
+
const ok = async (task) => {
|
|
686
|
+
await task;
|
|
687
|
+
ctx.console.refresh('newauth');
|
|
688
|
+
return { success: true };
|
|
689
|
+
};
|
|
690
|
+
ctx.console.addListener('newauth/getData', async () => {
|
|
691
|
+
return service.getConsoleData();
|
|
692
|
+
}, { authority: 4 });
|
|
693
|
+
ctx.console.addListener('newauth/setPolicy', async (payload) => {
|
|
694
|
+
return ok(service.setCommandPolicy(payload.scope, payload.roleId, payload.commandId, payload.state));
|
|
695
|
+
}, { authority: 4 });
|
|
696
|
+
ctx.console.addListener('newauth/setCommandStatus', async (payload) => {
|
|
697
|
+
return ok(service.setCommandStatus(payload.commandId, payload.status));
|
|
698
|
+
}, { authority: 4 });
|
|
699
|
+
ctx.console.addListener('newauth/setGuildOverride', async (payload) => {
|
|
700
|
+
return ok(service.setCommandGuildOverride(payload.commandId, payload.allowGuildOverride));
|
|
701
|
+
}, { authority: 4 });
|
|
702
|
+
ctx.console.addListener('newauth/applySuggestion', async (payload) => {
|
|
703
|
+
return ok(service.applySuggestedPolicy(payload.commandId, payload.scope, payload.mode));
|
|
704
|
+
}, { authority: 4 });
|
|
705
|
+
ctx.console.addListener('newauth/autoAssignPending', async (payload = {}) => {
|
|
706
|
+
const count = await service.autoAssignPending(payload.scope, payload.mode);
|
|
707
|
+
ctx.console.refresh('newauth');
|
|
708
|
+
return { success: true, count };
|
|
709
|
+
}, { authority: 4 });
|
|
710
|
+
ctx.console.addListener('newauth/createRole', async (payload) => {
|
|
711
|
+
return ok(service.createCustomRole(payload.id, payload.name, payload.scopeType));
|
|
712
|
+
}, { authority: 4 });
|
|
713
|
+
ctx.console.addListener('newauth/addMember', async (payload) => {
|
|
714
|
+
return ok(service.addRoleMember(payload.roleId, payload.uid, payload.scope));
|
|
715
|
+
}, { authority: 4 });
|
|
716
|
+
ctx.console.addListener('newauth/removeMember', async (payload) => {
|
|
717
|
+
return ok(service.removeRoleMember(payload.roleId, payload.uid, payload.scope));
|
|
718
|
+
}, { authority: 4 });
|
|
719
|
+
ctx.console.addListener('newauth/copyRolePolicies', async (payload) => {
|
|
720
|
+
const count = await service.copyRolePolicies(payload.sourceRoleId, payload.targetRoleId, payload.scope);
|
|
721
|
+
ctx.console.refresh('newauth');
|
|
722
|
+
return { success: true, count };
|
|
723
|
+
}, { authority: 4 });
|
|
724
|
+
}
|
|
489
725
|
function registerManagementCommands(ctx, config, service) {
|
|
490
726
|
const authority = config.legacyAdminAuthority;
|
|
491
727
|
ctx.command('newauth', '管理新权限系统', { authority })
|
|
@@ -575,6 +811,11 @@ function registerManagementCommands(ctx, config, service) {
|
|
|
575
811
|
await service.removeRoleMember(roleId, uid, scope);
|
|
576
812
|
return `已移除成员:${roleId} ${uid} ${scope}`;
|
|
577
813
|
});
|
|
814
|
+
ctx.command('newauth.role.copy <sourceRoleId> <targetRoleId> [scope]', '复制角色权限', { authority })
|
|
815
|
+
.action(async (_, sourceRoleId, targetRoleId, scope = 'global') => {
|
|
816
|
+
const count = await service.copyRolePolicies(sourceRoleId, targetRoleId, scope);
|
|
817
|
+
return `已复制 ${count} 条策略:${sourceRoleId} -> ${targetRoleId} (${scope})`;
|
|
818
|
+
});
|
|
578
819
|
}
|
|
579
820
|
function inferLegacyAuthority(command) {
|
|
580
821
|
if (typeof command.config.authority === 'number')
|
|
@@ -586,6 +827,36 @@ function inferLegacyAuthority(command) {
|
|
|
586
827
|
}
|
|
587
828
|
return 1;
|
|
588
829
|
}
|
|
830
|
+
function getLegacySuggestion(authority) {
|
|
831
|
+
if (authority <= 0) {
|
|
832
|
+
return {
|
|
833
|
+
roles: ['guest', 'guild-member', 'guild-admin', 'guild-owner'],
|
|
834
|
+
label: '访客/群成员及以上',
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
if (authority === 1) {
|
|
838
|
+
return {
|
|
839
|
+
roles: ['guild-member', 'guild-admin', 'guild-owner'],
|
|
840
|
+
label: '群成员及以上',
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (authority === 2) {
|
|
844
|
+
return {
|
|
845
|
+
roles: ['guild-admin', 'guild-owner'],
|
|
846
|
+
label: '群管理员及以上',
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (authority === 3) {
|
|
850
|
+
return {
|
|
851
|
+
roles: ['guild-owner'],
|
|
852
|
+
label: '群主',
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
roles: ['bot-admin'],
|
|
857
|
+
label: '仅 Bot 管理员',
|
|
858
|
+
};
|
|
859
|
+
}
|
|
589
860
|
function inferPlugin(command) {
|
|
590
861
|
const source = command.caller?.scope || command.ctx?.scope;
|
|
591
862
|
const plugin = source?.plugin?.name || source?.uid || source?.id || 'unknown';
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-new-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Role and scope based command permission layer for Koishi.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"lib",
|
|
9
|
+
"dist",
|
|
9
10
|
"README.md",
|
|
10
11
|
"newauth.md"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
|
-
"build": "
|
|
14
|
+
"build": "npm run build:server && npm run build:client",
|
|
15
|
+
"build:server": "tsc -p tsconfig.json",
|
|
16
|
+
"build:client": "node scripts/build-client.mjs",
|
|
14
17
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
15
18
|
"prepack": "npm run build"
|
|
16
19
|
},
|
|
@@ -23,9 +26,17 @@
|
|
|
23
26
|
],
|
|
24
27
|
"license": "MIT",
|
|
25
28
|
"peerDependencies": {
|
|
29
|
+
"@koishijs/plugin-console": "^5.30.0",
|
|
26
30
|
"koishi": "^4.18.0"
|
|
27
31
|
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"@koishijs/plugin-console": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
28
37
|
"devDependencies": {
|
|
38
|
+
"@koishijs/client": "^5.30.11",
|
|
39
|
+
"@koishijs/plugin-console": "^5.30.11",
|
|
29
40
|
"@types/node": "^22.0.0",
|
|
30
41
|
"koishi": "4.18.11",
|
|
31
42
|
"typescript": "^5.8.0"
|