@mushinjs/plugin-console 0.0.0-stage → 0.2.0-next.20261113103537
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/LICENSE +21 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.js +35 -0
- package/dist/auth.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +409 -0
- package/dist/index.js.map +1 -0
- package/dist/static.js +64 -0
- package/dist/static.js.map +1 -0
- package/package.json +67 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Setobox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Replaces the admin token in a data directory and returns it. A running instance picks it up on the next sign-in. */
|
|
2
|
+
export declare function resetAdminToken(dataDir: string): Promise<string>;
|
|
3
|
+
export declare function readAdminToken(dataDir: string): Promise<string | undefined>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readJson, writeJsonAtomic } from "@mushinjs/core";
|
|
2
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
// src/auth.ts
|
|
5
|
+
/** Storage key of the auth record: `data/auth.json`. */
|
|
6
|
+
const AUTH_KEY = "auth";
|
|
7
|
+
function generateToken() {
|
|
8
|
+
return randomBytes(24).toString("base64url");
|
|
9
|
+
}
|
|
10
|
+
/** Older builds stored only a hash, which cannot be shown again; those records are replaced. */
|
|
11
|
+
function isAuthRecord(value) {
|
|
12
|
+
return typeof value?.token === "string" && value.token.length >= 16;
|
|
13
|
+
}
|
|
14
|
+
function tokensEqual(supplied, expected) {
|
|
15
|
+
if (!supplied || !expected) return false;
|
|
16
|
+
const a = Buffer.from(supplied);
|
|
17
|
+
const b = Buffer.from(expected);
|
|
18
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
19
|
+
}
|
|
20
|
+
/** Replaces the admin token in a data directory and returns it. A running instance picks it up on the next sign-in. */
|
|
21
|
+
async function resetAdminToken(dataDir) {
|
|
22
|
+
const token = generateToken();
|
|
23
|
+
await writeJsonAtomic(join(dataDir, `${AUTH_KEY}.json`), {
|
|
24
|
+
token,
|
|
25
|
+
createdAt: Date.now()
|
|
26
|
+
});
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
async function readAdminToken(dataDir) {
|
|
30
|
+
const record = await readJson(join(dataDir, `${AUTH_KEY}.json`));
|
|
31
|
+
return isAuthRecord(record) ? record.token : void 0;
|
|
32
|
+
}
|
|
33
|
+
export { AUTH_KEY, generateToken, isAuthRecord, readAdminToken, resetAdminToken, tokensEqual };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../src/auth.ts"],"sourcesContent":["import { randomBytes, timingSafeEqual } from 'node:crypto'\nimport { join } from 'node:path'\nimport { readJson, writeJsonAtomic } from '@mushinjs/core'\n\n/**\n * The admin token, stored in plain text next to the provider keys it protects, so it can be\n * shown again on every start. Anyone who can read the data directory already holds those keys.\n */\nexport interface AuthRecord {\n token: string\n createdAt: number\n}\n\n/** Storage key of the auth record: `data/auth.json`. */\nexport const AUTH_KEY = 'auth'\n\nexport function generateToken() {\n return randomBytes(24).toString('base64url')\n}\n\n/** Older builds stored only a hash, which cannot be shown again; those records are replaced. */\nexport function isAuthRecord(value: unknown): value is AuthRecord {\n return typeof (value as AuthRecord | undefined)?.token === 'string' && (value as AuthRecord).token.length >= 16\n}\n\nexport function tokensEqual(supplied: string | null | undefined, expected: string | undefined) {\n if (!supplied || !expected) return false\n const a = Buffer.from(supplied)\n const b = Buffer.from(expected)\n return a.length === b.length && timingSafeEqual(a, b)\n}\n\n/** Replaces the admin token in a data directory and returns it. A running instance picks it up on the next sign-in. */\nexport async function resetAdminToken(dataDir: string) {\n const token = generateToken()\n await writeJsonAtomic(join(dataDir, `${AUTH_KEY}.json`), { token, createdAt: Date.now() } satisfies AuthRecord)\n return token\n}\n\nexport async function readAdminToken(dataDir: string) {\n const record = await readJson<unknown>(join(dataDir, `${AUTH_KEY}.json`))\n return isAuthRecord(record) ? record.token : undefined\n}\n"],"mappings":";;;;;AAcA,MAAa,WAAW;AAExB,SAAgB,gBAAgB;CAC9B,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,WAAW;AAC7C;;AAGA,SAAgB,aAAa,OAAqC;CAChE,OAAO,OAAQ,OAAkC,UAAU,YAAa,MAAqB,MAAM,UAAU;AAC/G;AAEA,SAAgB,YAAY,UAAqC,UAA8B;CAC7F,IAAI,CAAC,YAAY,CAAC,UAAU,OAAO;CACnC,MAAM,IAAI,OAAO,KAAK,QAAQ;CAC9B,MAAM,IAAI,OAAO,KAAK,QAAQ;CAC9B,OAAO,EAAE,WAAW,EAAE,UAAU,gBAAgB,GAAG,CAAC;AACtD;;AAGA,eAAsB,gBAAgB,SAAiB;CACrD,MAAM,QAAQ,cAAc;CAC5B,MAAM,gBAAgB,KAAK,SAAS,GAAG,SAAS,MAAM,GAAG;EAAE;EAAO,WAAW,KAAK,IAAI;CAAE,CAAsB;CAC9G,OAAO;AACT;AAEA,eAAsB,eAAe,SAAiB;CACpD,MAAM,SAAS,MAAM,SAAkB,KAAK,SAAS,GAAG,SAAS,MAAM,CAAC;CACxE,OAAO,aAAa,MAAM,IAAI,OAAO,QAAQ,KAAA;AAC/C"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readAdminToken, resetAdminToken } from "./auth.js";
|
|
2
|
+
import { Context, LogTag, Service, z } from "@mushinjs/core";
|
|
3
|
+
declare module '@mushinjs/core' {
|
|
4
|
+
interface Context {
|
|
5
|
+
console: Console;
|
|
6
|
+
}
|
|
7
|
+
interface Events {
|
|
8
|
+
'console/ready'(info: SignInInfo): void;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** What the launcher prints so the user can sign in. Never sent through the logger. */
|
|
12
|
+
export interface SignInInfo {
|
|
13
|
+
url: string;
|
|
14
|
+
token: string;
|
|
15
|
+
fromEnv: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const name = "console";
|
|
18
|
+
export interface LogLine {
|
|
19
|
+
sn: number;
|
|
20
|
+
ts: number;
|
|
21
|
+
type: string;
|
|
22
|
+
name: string;
|
|
23
|
+
/** Source category from the logger name, for the dashboard's quick filters. */
|
|
24
|
+
tag: LogTag;
|
|
25
|
+
text: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Serves the dashboard, its HTTP API (one generic action endpoint) and a WebSocket for live events.
|
|
29
|
+
* Every request needs the admin token.
|
|
30
|
+
*/
|
|
31
|
+
export declare class Console extends Service {
|
|
32
|
+
config: {
|
|
33
|
+
path: string;
|
|
34
|
+
};
|
|
35
|
+
static inject: string[];
|
|
36
|
+
static Config: z.ZodObject<{
|
|
37
|
+
path: z.ZodDefault<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
private home;
|
|
40
|
+
private clients;
|
|
41
|
+
private auth?;
|
|
42
|
+
private reloadedAt;
|
|
43
|
+
private envToken;
|
|
44
|
+
/** Set once the dashboard is being served. */
|
|
45
|
+
ready?: SignInInfo;
|
|
46
|
+
constructor(ctx: Context, config: {
|
|
47
|
+
path: string;
|
|
48
|
+
});
|
|
49
|
+
[Service.init](): AsyncGenerator<() => void, void, unknown>;
|
|
50
|
+
private ensureToken;
|
|
51
|
+
private verify;
|
|
52
|
+
/** Verifies a token; on a miss, re-reads data/auth.json so `mushin token reset` works without a restart. */
|
|
53
|
+
private authenticate;
|
|
54
|
+
private authorize;
|
|
55
|
+
private isBotLog;
|
|
56
|
+
broadcast(name: string, data: unknown): void;
|
|
57
|
+
private attach;
|
|
58
|
+
/** Runs a client action in the caller's dashboard tab, or the most recently active one. */
|
|
59
|
+
private relay;
|
|
60
|
+
private defineActions;
|
|
61
|
+
}
|
|
62
|
+
export { Console as default, readAdminToken, resetAdminToken };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { AUTH_KEY, generateToken, isAuthRecord, readAdminToken, resetAdminToken, tokensEqual } from "./auth.js";
|
|
2
|
+
import { MISSING_BUILD_PAGE, dashboardRoot, readStatic } from "./static.js";
|
|
3
|
+
import { LOG_TAGS, Logger, MushinError, Service, field, logTag, randomId, toErrorPayload, z } from "@mushinjs/core";
|
|
4
|
+
// src/index.ts
|
|
5
|
+
const name = "console";
|
|
6
|
+
const CLIENT_CALL_TIMEOUT = 15e3;
|
|
7
|
+
/** Client actions that wait for the user get longer. */
|
|
8
|
+
const CLIENT_CALL_TIMEOUTS = { "ui.ask": 9e5 };
|
|
9
|
+
/** Events forwarded to every signed-in dashboard. */
|
|
10
|
+
const FORWARDED_EVENTS = [
|
|
11
|
+
"actions/changed",
|
|
12
|
+
"manager/changed",
|
|
13
|
+
"providers/changed",
|
|
14
|
+
"bots/changed",
|
|
15
|
+
"bots/status",
|
|
16
|
+
"bots/message",
|
|
17
|
+
"bots/log",
|
|
18
|
+
"agent/event",
|
|
19
|
+
"agent/changed",
|
|
20
|
+
"flows/changed",
|
|
21
|
+
"flows/run",
|
|
22
|
+
"flows/step",
|
|
23
|
+
"flows/tests",
|
|
24
|
+
"flows/activity",
|
|
25
|
+
"flows/paused",
|
|
26
|
+
"stats/sample",
|
|
27
|
+
"permissions/changed",
|
|
28
|
+
"personas/changed",
|
|
29
|
+
"knowledge/changed",
|
|
30
|
+
"knowledge/progress"
|
|
31
|
+
];
|
|
32
|
+
const plainExporter = {
|
|
33
|
+
colors: false,
|
|
34
|
+
export() {}
|
|
35
|
+
};
|
|
36
|
+
function formatLog(message) {
|
|
37
|
+
return {
|
|
38
|
+
sn: message.sn,
|
|
39
|
+
ts: message.ts,
|
|
40
|
+
type: message.type,
|
|
41
|
+
name: message.name,
|
|
42
|
+
tag: logTag(message.name),
|
|
43
|
+
text: Logger.format(plainExporter, message)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Serves the dashboard, its HTTP API (one generic action endpoint) and a WebSocket for live events.
|
|
48
|
+
* Every request needs the admin token.
|
|
49
|
+
*/
|
|
50
|
+
var Console = class extends Service {
|
|
51
|
+
config;
|
|
52
|
+
static inject = [
|
|
53
|
+
"server",
|
|
54
|
+
"actions",
|
|
55
|
+
"storage"
|
|
56
|
+
];
|
|
57
|
+
static Config = z.object({ path: z.string().default("/api").meta(field("API path", "Base path of the HTTP API", { "zh-CN": {
|
|
58
|
+
title: "API 路径",
|
|
59
|
+
description: "HTTP API 的基础路径"
|
|
60
|
+
} })) });
|
|
61
|
+
home;
|
|
62
|
+
clients = /* @__PURE__ */ new Map();
|
|
63
|
+
auth;
|
|
64
|
+
reloadedAt = 0;
|
|
65
|
+
envToken = process.env.MUSHIN_ADMIN_TOKEN;
|
|
66
|
+
/** Set once the dashboard is being served. */
|
|
67
|
+
ready;
|
|
68
|
+
constructor(ctx, config) {
|
|
69
|
+
super(ctx, "console");
|
|
70
|
+
this.config = config;
|
|
71
|
+
this.home = ctx;
|
|
72
|
+
}
|
|
73
|
+
async *[Service.init]() {
|
|
74
|
+
const devDashboard = process.env.MUSHIN_DEV_DASHBOARD;
|
|
75
|
+
await this.ensureToken();
|
|
76
|
+
const { server, actions } = this.home;
|
|
77
|
+
const base = this.config.path;
|
|
78
|
+
yield actions.setClientRelay((name, input, caller) => this.relay(name, input, caller));
|
|
79
|
+
yield* this.defineActions();
|
|
80
|
+
server.get(`${base}/health`, async (_req, res) => {
|
|
81
|
+
res.json({ ok: true });
|
|
82
|
+
});
|
|
83
|
+
server.post(`${base}/login`, async (req, res) => {
|
|
84
|
+
const body = await readBody(req).catch(() => void 0);
|
|
85
|
+
if (!await this.authenticate(typeof body?.token === "string" ? body.token.trim() : void 0)) {
|
|
86
|
+
res.status = 401;
|
|
87
|
+
res.json({
|
|
88
|
+
ok: false,
|
|
89
|
+
error: {
|
|
90
|
+
code: "unauthorized",
|
|
91
|
+
message: "Wrong admin token"
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
res.json({ ok: true });
|
|
97
|
+
});
|
|
98
|
+
server.get(`${base}/actions`, async (req, res) => {
|
|
99
|
+
if (!await this.authorize(req, res)) return;
|
|
100
|
+
res.json({
|
|
101
|
+
ok: true,
|
|
102
|
+
result: actions.list()
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
server.post(`${base}/actions/:name`, async (req, res) => {
|
|
106
|
+
if (!await this.authorize(req, res)) return;
|
|
107
|
+
try {
|
|
108
|
+
const body = await readBody(req);
|
|
109
|
+
const result = await actions.call(req.params.name, body ?? {}, { kind: "api" });
|
|
110
|
+
res.json({
|
|
111
|
+
ok: true,
|
|
112
|
+
result: result ?? null
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
res.status = error instanceof MushinError ? error.status : 500;
|
|
116
|
+
if (!(error instanceof MushinError)) this.home.logger("console").warn(error);
|
|
117
|
+
res.json({
|
|
118
|
+
ok: false,
|
|
119
|
+
error: toErrorPayload(error)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
server.ws(`${base}/ws`, async (_req, accept) => {
|
|
124
|
+
this.attach(await accept());
|
|
125
|
+
});
|
|
126
|
+
const root = dashboardRoot();
|
|
127
|
+
server.get(/^(?!\/api(?:\/|$)).*/, async (req, res) => {
|
|
128
|
+
if (devDashboard) {
|
|
129
|
+
res.status = 302;
|
|
130
|
+
res.headers.set("location", `${devDashboard}${req.url}`);
|
|
131
|
+
res.headers.set("cache-control", "no-store");
|
|
132
|
+
res.text("");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const file = root ? await readStatic(root, req.path) : void 0;
|
|
136
|
+
if (!file) {
|
|
137
|
+
res.headers.set("content-type", "text/html; charset=utf-8");
|
|
138
|
+
res.status = root ? 404 : 200;
|
|
139
|
+
res.text(root ? "Not found" : MISSING_BUILD_PAGE);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
res.status = 200;
|
|
143
|
+
res.headers.set("content-type", file.type);
|
|
144
|
+
res.headers.set("cache-control", file.immutable ? "public, max-age=31536000, immutable" : "no-cache");
|
|
145
|
+
res.bytes(new Uint8Array(file.body));
|
|
146
|
+
});
|
|
147
|
+
for (const name of FORWARDED_EVENTS) yield this.home.on(name, (data) => this.broadcast(name, data));
|
|
148
|
+
this.ready = {
|
|
149
|
+
url: devDashboard ?? server.baseUrl,
|
|
150
|
+
token: this.envToken ?? this.auth.token,
|
|
151
|
+
fromEnv: !!this.envToken
|
|
152
|
+
};
|
|
153
|
+
this.home.emit("console/ready", this.ready);
|
|
154
|
+
this.home.logger("console").info("dashboard at %s", this.ready.url);
|
|
155
|
+
yield this.home.logger.exporter({
|
|
156
|
+
colors: false,
|
|
157
|
+
export: (message) => {
|
|
158
|
+
if (!this.isBotLog(message)) this.broadcast("log", formatLog(message));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
yield () => {
|
|
162
|
+
for (const client of this.clients.values()) client.socket.close(1001, "console stopped");
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async ensureToken() {
|
|
166
|
+
if (this.envToken) {
|
|
167
|
+
this.home.logger("console").info("using the admin token from MUSHIN_ADMIN_TOKEN");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const stored = await this.home.storage.get(AUTH_KEY);
|
|
171
|
+
if (isAuthRecord(stored)) {
|
|
172
|
+
this.auth = stored;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.auth = {
|
|
176
|
+
token: generateToken(),
|
|
177
|
+
createdAt: Date.now()
|
|
178
|
+
};
|
|
179
|
+
await this.home.storage.set(AUTH_KEY, this.auth);
|
|
180
|
+
this.home.logger("console").info(stored ? "replaced an unreadable admin token record" : "created the admin token");
|
|
181
|
+
}
|
|
182
|
+
verify(token) {
|
|
183
|
+
return tokensEqual(token, this.envToken ?? this.auth?.token);
|
|
184
|
+
}
|
|
185
|
+
/** Verifies a token; on a miss, re-reads data/auth.json so `mushin token reset` works without a restart. */
|
|
186
|
+
async authenticate(token) {
|
|
187
|
+
if (this.verify(token)) return true;
|
|
188
|
+
if (this.envToken || !token || Date.now() - this.reloadedAt < 1e3) return false;
|
|
189
|
+
this.reloadedAt = Date.now();
|
|
190
|
+
const stored = await this.home.storage.get(AUTH_KEY).catch(() => void 0);
|
|
191
|
+
if (!isAuthRecord(stored) || stored.token === this.auth?.token) return false;
|
|
192
|
+
this.auth = stored;
|
|
193
|
+
if (this.ready) this.ready = {
|
|
194
|
+
...this.ready,
|
|
195
|
+
token: stored.token
|
|
196
|
+
};
|
|
197
|
+
return this.verify(token);
|
|
198
|
+
}
|
|
199
|
+
async authorize(req, res) {
|
|
200
|
+
const token = (req.headers.get("authorization") ?? "").replace(/^Bearer\s+/i, "");
|
|
201
|
+
if (await this.authenticate(token)) return true;
|
|
202
|
+
res.status = 401;
|
|
203
|
+
res.json({
|
|
204
|
+
ok: false,
|
|
205
|
+
error: {
|
|
206
|
+
code: "unauthorized",
|
|
207
|
+
message: "Sign in with the admin token"
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
isBotLog(message) {
|
|
213
|
+
return this.home.get("bots")?.isBotLog(message) ?? false;
|
|
214
|
+
}
|
|
215
|
+
broadcast(name, data) {
|
|
216
|
+
const frame = JSON.stringify({
|
|
217
|
+
type: "event",
|
|
218
|
+
name,
|
|
219
|
+
data
|
|
220
|
+
});
|
|
221
|
+
for (const client of this.clients.values()) if (client.authed && client.socket.readyState === 1) client.socket.send(frame);
|
|
222
|
+
}
|
|
223
|
+
attach(socket) {
|
|
224
|
+
const client = {
|
|
225
|
+
id: randomId(),
|
|
226
|
+
socket,
|
|
227
|
+
authed: false,
|
|
228
|
+
lastActive: Date.now(),
|
|
229
|
+
calls: /* @__PURE__ */ new Map()
|
|
230
|
+
};
|
|
231
|
+
this.clients.set(client.id, client);
|
|
232
|
+
const send = (data) => socket.readyState === 1 && socket.send(JSON.stringify(data));
|
|
233
|
+
const authTimer = setTimeout(() => {
|
|
234
|
+
if (!client.authed) socket.close(4401, "authentication timeout");
|
|
235
|
+
}, 1e4);
|
|
236
|
+
socket.on("close", () => {
|
|
237
|
+
clearTimeout(authTimer);
|
|
238
|
+
this.clients.delete(client.id);
|
|
239
|
+
for (const call of client.calls.values()) {
|
|
240
|
+
clearTimeout(call.timer);
|
|
241
|
+
call.reject(new MushinError("unavailable", "The dashboard tab was closed"));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
let authenticating;
|
|
245
|
+
socket.on("message", async (raw) => {
|
|
246
|
+
let message;
|
|
247
|
+
try {
|
|
248
|
+
message = JSON.parse(raw.toString());
|
|
249
|
+
} catch {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
client.lastActive = Date.now();
|
|
253
|
+
if (!client.authed) {
|
|
254
|
+
if (message.type === "auth" && !authenticating) {
|
|
255
|
+
authenticating = this.authenticate(message.token);
|
|
256
|
+
if (await authenticating) {
|
|
257
|
+
client.authed = true;
|
|
258
|
+
client.page = message.page;
|
|
259
|
+
clearTimeout(authTimer);
|
|
260
|
+
send({
|
|
261
|
+
type: "ready",
|
|
262
|
+
client: client.id
|
|
263
|
+
});
|
|
264
|
+
} else socket.close(4401, "wrong admin token");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!authenticating) {
|
|
268
|
+
socket.close(4401, "authenticate first");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (!await authenticating) return;
|
|
272
|
+
}
|
|
273
|
+
switch (message.type) {
|
|
274
|
+
case "page":
|
|
275
|
+
client.page = typeof message.path === "string" ? message.path : void 0;
|
|
276
|
+
return;
|
|
277
|
+
case "call": {
|
|
278
|
+
const caller = {
|
|
279
|
+
kind: "api",
|
|
280
|
+
client: client.id
|
|
281
|
+
};
|
|
282
|
+
try {
|
|
283
|
+
const input = message.action === "agent.send" ? {
|
|
284
|
+
page: client.page,
|
|
285
|
+
...message.input
|
|
286
|
+
} : message.input;
|
|
287
|
+
const result = await this.home.actions.call(String(message.action), input ?? {}, caller);
|
|
288
|
+
send({
|
|
289
|
+
type: "result",
|
|
290
|
+
id: message.id,
|
|
291
|
+
ok: true,
|
|
292
|
+
result: result ?? null
|
|
293
|
+
});
|
|
294
|
+
} catch (error) {
|
|
295
|
+
send({
|
|
296
|
+
type: "result",
|
|
297
|
+
id: message.id,
|
|
298
|
+
ok: false,
|
|
299
|
+
error: toErrorPayload(error)
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
case "client-result": {
|
|
305
|
+
const call = client.calls.get(String(message.id));
|
|
306
|
+
if (!call) return;
|
|
307
|
+
client.calls.delete(String(message.id));
|
|
308
|
+
clearTimeout(call.timer);
|
|
309
|
+
if (message.ok) call.resolve(message.result ?? { ok: true });
|
|
310
|
+
else call.reject(new MushinError("failed", message.error?.message ?? "The dashboard could not run this action"));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** Runs a client action in the caller's dashboard tab, or the most recently active one. */
|
|
316
|
+
relay(name, input, caller) {
|
|
317
|
+
let client = caller.client ? this.clients.get(caller.client) : void 0;
|
|
318
|
+
if (!client?.authed) client = [...this.clients.values()].filter((item) => item.authed).sort((a, b) => b.lastActive - a.lastActive)[0];
|
|
319
|
+
if (!client) return Promise.reject(new MushinError("unavailable", "No dashboard is open"));
|
|
320
|
+
const target = client;
|
|
321
|
+
const id = randomId();
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const timer = setTimeout(() => {
|
|
324
|
+
target.calls.delete(id);
|
|
325
|
+
reject(new MushinError("unavailable", "The dashboard did not answer"));
|
|
326
|
+
}, CLIENT_CALL_TIMEOUTS[name] ?? CLIENT_CALL_TIMEOUT);
|
|
327
|
+
target.calls.set(id, {
|
|
328
|
+
resolve,
|
|
329
|
+
reject,
|
|
330
|
+
timer
|
|
331
|
+
});
|
|
332
|
+
target.socket.send(JSON.stringify({
|
|
333
|
+
type: "client-call",
|
|
334
|
+
id,
|
|
335
|
+
action: name,
|
|
336
|
+
input
|
|
337
|
+
}));
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
*defineActions() {
|
|
341
|
+
const actions = this.home.actions;
|
|
342
|
+
yield actions.define({
|
|
343
|
+
name: "ui.navigate",
|
|
344
|
+
description: "Open a dashboard page in the user's browser, e.g. /bots/<bot id>, /flows/<flow id> or /providers.",
|
|
345
|
+
risk: "read",
|
|
346
|
+
client: true,
|
|
347
|
+
input: z.object({ path: z.string().regex(/^\//).describe("Dashboard path starting with /") })
|
|
348
|
+
});
|
|
349
|
+
yield actions.define({
|
|
350
|
+
name: "ui.highlight",
|
|
351
|
+
description: "Point at a control on the current dashboard page. Targets: providers.add, bots.add, bot.connection, bot.plugins, bot.runs, bot.live, bot.logs, flows.add, flows.bots, flows.save, flows.test, agent.input.",
|
|
352
|
+
risk: "read",
|
|
353
|
+
client: true,
|
|
354
|
+
input: z.object({
|
|
355
|
+
target: z.string().describe("Highlight target id"),
|
|
356
|
+
message: z.string().optional().describe("Short hint shown next to the control")
|
|
357
|
+
})
|
|
358
|
+
});
|
|
359
|
+
yield actions.define({
|
|
360
|
+
name: "ui.ask",
|
|
361
|
+
description: "Ask the user a multiple-choice question in the Agent panel and wait for the answer. Use it when a choice is theirs to make (which bot, which provider, whether to overwrite). Returns { choice, label } or { other } when they type their own answer.",
|
|
362
|
+
risk: "read",
|
|
363
|
+
client: true,
|
|
364
|
+
input: z.object({
|
|
365
|
+
question: z.string().min(1).max(300),
|
|
366
|
+
options: z.array(z.object({
|
|
367
|
+
label: z.string().min(1).max(80),
|
|
368
|
+
description: z.string().max(200).optional()
|
|
369
|
+
})).min(2).max(6),
|
|
370
|
+
recommended: z.number().int().min(0).optional().describe("Index of the option you recommend"),
|
|
371
|
+
allowOther: z.boolean().optional().describe("Let the user type their own answer")
|
|
372
|
+
})
|
|
373
|
+
});
|
|
374
|
+
yield actions.define({
|
|
375
|
+
name: "system.info",
|
|
376
|
+
description: "Server address and runtime versions. The address is what NapCat should use in reverse mode.",
|
|
377
|
+
risk: "read",
|
|
378
|
+
input: z.object({}),
|
|
379
|
+
handler: () => ({
|
|
380
|
+
baseUrl: this.home.server.baseUrl,
|
|
381
|
+
node: process.version,
|
|
382
|
+
platform: process.platform,
|
|
383
|
+
uptime: Math.round(process.uptime())
|
|
384
|
+
})
|
|
385
|
+
});
|
|
386
|
+
yield actions.define({
|
|
387
|
+
name: "logs.recent",
|
|
388
|
+
description: "Recent log lines of Mushin.js itself, oldest first, each with a source tag. Lines written inside a bot are in bots.logs instead.",
|
|
389
|
+
risk: "read",
|
|
390
|
+
input: z.object({
|
|
391
|
+
limit: z.number().int().min(1).max(1e3).optional(),
|
|
392
|
+
tag: z.enum(LOG_TAGS).optional().describe("Only lines from this source: plugin, bot, adapter, flow, provider, agent, system or other")
|
|
393
|
+
}),
|
|
394
|
+
handler: ({ limit, tag }) => this.home.logger.buffer.filter((message) => !this.isBotLog(message) && (!tag || logTag(message.name) === tag)).slice(-(limit ?? 200)).map(formatLog)
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
async function readBody(req) {
|
|
399
|
+
const text = await req.text();
|
|
400
|
+
if (!text) return void 0;
|
|
401
|
+
try {
|
|
402
|
+
return JSON.parse(text);
|
|
403
|
+
} catch {
|
|
404
|
+
throw new MushinError("invalid_input", "Request body must be JSON");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
export { Console, Console as default, name, readAdminToken, resetAdminToken };
|
|
408
|
+
|
|
409
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { WebSocket } from 'ws'\nimport {\n type ActionCaller, Context, type Exporter, field, Logger, LOG_TAGS, type LogTag, logTag, type Message, MushinError, randomId, Service, toErrorPayload, z,\n} from '@mushinjs/core'\nimport type {} from '@cordisjs/plugin-server'\nimport type {} from '@mushinjs/plugin-agent'\nimport type {} from '@mushinjs/plugin-bots'\nimport type {} from '@mushinjs/plugin-flows'\nimport type {} from '@mushinjs/plugin-providers'\nimport type {} from '@mushinjs/plugin-stats'\nimport type {} from '@mushinjs/plugin-storage'\nimport { AUTH_KEY, type AuthRecord, generateToken, isAuthRecord, tokensEqual } from './auth'\nimport { dashboardRoot, MISSING_BUILD_PAGE, readStatic } from './static'\n\nexport { readAdminToken, resetAdminToken } from './auth'\n\ndeclare module '@mushinjs/core' {\n interface Context {\n console: Console\n }\n\n interface Events {\n 'console/ready'(info: SignInInfo): void\n }\n}\n\n/** What the launcher prints so the user can sign in. Never sent through the logger. */\nexport interface SignInInfo {\n url: string\n token: string\n fromEnv: boolean\n}\n\nexport const name = 'console'\n\ninterface Client {\n id: string\n socket: WebSocket\n authed: boolean\n page?: string\n lastActive: number\n calls: Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }>\n}\n\nexport interface LogLine {\n sn: number\n ts: number\n type: string\n name: string\n /** Source category from the logger name, for the dashboard's quick filters. */\n tag: LogTag\n text: string\n}\n\nconst CLIENT_CALL_TIMEOUT = 15_000\n/** Client actions that wait for the user get longer. */\nconst CLIENT_CALL_TIMEOUTS: Record<string, number> = { 'ui.ask': 15 * 60_000 }\n\n/** Events forwarded to every signed-in dashboard. */\nconst FORWARDED_EVENTS = [\n 'actions/changed', 'manager/changed', 'providers/changed',\n 'bots/changed', 'bots/status', 'bots/message', 'bots/log', 'agent/event', 'agent/changed',\n 'flows/changed', 'flows/run', 'flows/step', 'flows/tests', 'flows/activity', 'flows/paused', 'stats/sample', 'permissions/changed', 'personas/changed',\n 'knowledge/changed', 'knowledge/progress',\n] as const\n\nconst plainExporter: Exporter = { colors: false, export() {} }\n\nfunction formatLog(message: Message): LogLine {\n return {\n sn: message.sn,\n ts: message.ts,\n type: message.type,\n name: message.name,\n tag: logTag(message.name),\n text: Logger.format(plainExporter, message),\n }\n}\n\n/**\n * Serves the dashboard, its HTTP API (one generic action endpoint) and a WebSocket for live events.\n * Every request needs the admin token.\n */\nexport class Console extends Service {\n static inject = ['server', 'actions', 'storage']\n static Config = z.object({\n path: z.string().default('/api').meta(field('API path', 'Base path of the HTTP API', {\n 'zh-CN': { title: 'API 路径', description: 'HTTP API 的基础路径' },\n })),\n })\n\n private home: Context\n private clients = new Map<string, Client>()\n private auth?: AuthRecord\n private reloadedAt = 0\n private envToken = process.env.MUSHIN_ADMIN_TOKEN\n /** Set once the dashboard is being served. */\n ready?: SignInInfo\n\n constructor(ctx: Context, public config: { path: string }) {\n super(ctx, 'console')\n this.home = ctx\n }\n\n async *[Service.init]() {\n const devDashboard = process.env.MUSHIN_DEV_DASHBOARD\n await this.ensureToken()\n const { server, actions } = this.home\n const base = this.config.path\n\n yield actions.setClientRelay((name, input, caller) => this.relay(name, input, caller))\n yield* this.defineActions()\n\n // Health and login are the only routes that work without a token.\n server.get(`${base}/health`, async (_req, res) => {\n res.json({ ok: true })\n })\n\n server.post(`${base}/login`, async (req, res) => {\n const body = await readBody(req).catch(() => undefined)\n if (!(await this.authenticate(typeof body?.token === 'string' ? body.token.trim() : undefined))) {\n res.status = 401\n res.json({ ok: false, error: { code: 'unauthorized', message: 'Wrong admin token' } })\n return\n }\n res.json({ ok: true })\n })\n\n server.get(`${base}/actions`, async (req, res) => {\n if (!(await this.authorize(req, res))) return\n res.json({ ok: true, result: actions.list() })\n })\n\n server.post(`${base}/actions/:name`, async (req, res) => {\n if (!(await this.authorize(req, res))) return\n try {\n const body = await readBody(req)\n const result = await actions.call(req.params.name, body ?? {}, { kind: 'api' })\n res.json({ ok: true, result: result ?? null })\n } catch (error) {\n res.status = error instanceof MushinError ? error.status : 500\n if (!(error instanceof MushinError)) this.home.logger('console').warn(error)\n res.json({ ok: false, error: toErrorPayload(error) })\n }\n })\n\n server.ws(`${base}/ws`, async (_req, accept) => {\n this.attach(await accept())\n })\n\n // Everything outside the API is the dashboard (with SPA fallback).\n const root = dashboardRoot()\n server.get(/^(?!\\/api(?:\\/|$)).*/, async (req, res) => {\n if (devDashboard) {\n res.status = 302\n res.headers.set('location', `${devDashboard}${req.url}`)\n res.headers.set('cache-control', 'no-store')\n res.text('')\n return\n }\n const file = root ? await readStatic(root, req.path) : undefined\n if (!file) {\n res.headers.set('content-type', 'text/html; charset=utf-8')\n res.status = root ? 404 : 200\n res.text(root ? 'Not found' : MISSING_BUILD_PAGE)\n return\n }\n res.status = 200\n res.headers.set('content-type', file.type)\n res.headers.set('cache-control', file.immutable ? 'public, max-age=31536000, immutable' : 'no-cache')\n res.bytes(new Uint8Array(file.body))\n })\n\n for (const name of FORWARDED_EVENTS) {\n yield this.home.on(name, (data: unknown) => this.broadcast(name, data))\n }\n\n // The launcher prints the sign-in link from this event; the token never goes to the log buffer,\n // which the dashboard and the Agent can read.\n this.ready = { url: devDashboard ?? server.baseUrl, token: this.envToken ?? this.auth!.token, fromEnv: !!this.envToken }\n this.home.emit('console/ready', this.ready)\n this.home.logger('console').info('dashboard at %s', this.ready.url)\n yield this.home.logger.exporter({\n colors: false,\n // Lines written inside a bot go to that bot's own log (`bots/log`), not the global one.\n export: (message) => {\n if (!this.isBotLog(message)) this.broadcast('log', formatLog(message))\n },\n })\n yield () => {\n for (const client of this.clients.values()) client.socket.close(1001, 'console stopped')\n }\n }\n\n private async ensureToken() {\n if (this.envToken) {\n this.home.logger('console').info('using the admin token from MUSHIN_ADMIN_TOKEN')\n return\n }\n const stored = await this.home.storage.get<unknown>(AUTH_KEY)\n if (isAuthRecord(stored)) {\n this.auth = stored\n return\n }\n // No record yet, or one from an older build that kept only a hash: make a token that can be shown.\n this.auth = { token: generateToken(), createdAt: Date.now() }\n await this.home.storage.set(AUTH_KEY, this.auth)\n this.home.logger('console').info(stored ? 'replaced an unreadable admin token record' : 'created the admin token')\n }\n\n private verify(token: string | null | undefined) {\n return tokensEqual(token, this.envToken ?? this.auth?.token)\n }\n\n /** Verifies a token; on a miss, re-reads data/auth.json so `mushin token reset` works without a restart. */\n private async authenticate(token: string | null | undefined) {\n if (this.verify(token)) return true\n if (this.envToken || !token || Date.now() - this.reloadedAt < 1000) return false\n this.reloadedAt = Date.now()\n const stored = await this.home.storage.get<unknown>(AUTH_KEY).catch(() => undefined)\n if (!isAuthRecord(stored) || stored.token === this.auth?.token) return false\n this.auth = stored\n if (this.ready) this.ready = { ...this.ready, token: stored.token }\n return this.verify(token)\n }\n\n private async authorize(req: { headers: Headers }, res: { status: number; json(data: unknown): unknown }) {\n const header = req.headers.get('authorization') ?? ''\n const token = header.replace(/^Bearer\\s+/i, '')\n if (await this.authenticate(token)) return true\n res.status = 401\n res.json({ ok: false, error: { code: 'unauthorized', message: 'Sign in with the admin token' } })\n return false\n }\n\n private isBotLog(message: Message) {\n return this.home.get('bots')?.isBotLog(message) ?? false\n }\n\n broadcast(name: string, data: unknown) {\n const frame = JSON.stringify({ type: 'event', name, data })\n for (const client of this.clients.values()) {\n if (client.authed && client.socket.readyState === 1) client.socket.send(frame)\n }\n }\n\n private attach(socket: WebSocket) {\n const client: Client = { id: randomId(), socket, authed: false, lastActive: Date.now(), calls: new Map() }\n this.clients.set(client.id, client)\n const send = (data: unknown) => socket.readyState === 1 && socket.send(JSON.stringify(data))\n const authTimer = setTimeout(() => {\n if (!client.authed) socket.close(4401, 'authentication timeout')\n }, 10_000)\n\n socket.on('close', () => {\n clearTimeout(authTimer)\n this.clients.delete(client.id)\n for (const call of client.calls.values()) {\n clearTimeout(call.timer)\n call.reject(new MushinError('unavailable', 'The dashboard tab was closed'))\n }\n })\n\n // Messages can arrive while the token is still being checked (the tab reports its page as soon\n // as the socket opens); they wait for the check instead of failing it.\n let authenticating: Promise<boolean> | undefined\n socket.on('message', async (raw) => {\n let message: any\n try {\n message = JSON.parse(raw.toString())\n } catch {\n return\n }\n client.lastActive = Date.now()\n if (!client.authed) {\n if (message.type === 'auth' && !authenticating) {\n authenticating = this.authenticate(message.token)\n if (await authenticating) {\n client.authed = true\n client.page = message.page\n clearTimeout(authTimer)\n send({ type: 'ready', client: client.id })\n } else {\n socket.close(4401, 'wrong admin token')\n }\n return\n }\n if (!authenticating) {\n socket.close(4401, 'authenticate first')\n return\n }\n if (!await authenticating) return\n }\n switch (message.type) {\n case 'page':\n client.page = typeof message.path === 'string' ? message.path : undefined\n return\n case 'call': {\n const caller: ActionCaller = { kind: 'api', client: client.id }\n try {\n const input = message.action === 'agent.send' ? { page: client.page, ...message.input } : message.input\n const result = await this.home.actions.call(String(message.action), input ?? {}, caller)\n send({ type: 'result', id: message.id, ok: true, result: result ?? null })\n } catch (error) {\n send({ type: 'result', id: message.id, ok: false, error: toErrorPayload(error) })\n }\n return\n }\n case 'client-result': {\n const call = client.calls.get(String(message.id))\n if (!call) return\n client.calls.delete(String(message.id))\n clearTimeout(call.timer)\n if (message.ok) call.resolve(message.result ?? { ok: true })\n else call.reject(new MushinError('failed', message.error?.message ?? 'The dashboard could not run this action'))\n }\n }\n })\n }\n\n /** Runs a client action in the caller's dashboard tab, or the most recently active one. */\n private relay(name: string, input: unknown, caller: ActionCaller) {\n let client = caller.client ? this.clients.get(caller.client) : undefined\n if (!client?.authed) {\n client = [...this.clients.values()].filter((item) => item.authed).sort((a, b) => b.lastActive - a.lastActive)[0]\n }\n if (!client) return Promise.reject(new MushinError('unavailable', 'No dashboard is open'))\n const target = client\n const id = randomId()\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n target.calls.delete(id)\n reject(new MushinError('unavailable', 'The dashboard did not answer'))\n }, CLIENT_CALL_TIMEOUTS[name] ?? CLIENT_CALL_TIMEOUT)\n target.calls.set(id, { resolve, reject, timer })\n target.socket.send(JSON.stringify({ type: 'client-call', id, action: name, input }))\n })\n }\n\n private *defineActions() {\n const actions = this.home.actions\n\n yield actions.define({\n name: 'ui.navigate',\n description: 'Open a dashboard page in the user\\'s browser, e.g. /bots/<bot id>, /flows/<flow id> or /providers.',\n risk: 'read',\n client: true,\n input: z.object({ path: z.string().regex(/^\\//).describe('Dashboard path starting with /') }),\n })\n\n yield actions.define({\n name: 'ui.highlight',\n description: 'Point at a control on the current dashboard page. Targets: providers.add, bots.add, bot.connection, bot.plugins, bot.runs, bot.live, bot.logs, flows.add, flows.bots, flows.save, flows.test, agent.input.',\n risk: 'read',\n client: true,\n input: z.object({\n target: z.string().describe('Highlight target id'),\n message: z.string().optional().describe('Short hint shown next to the control'),\n }),\n })\n\n yield actions.define({\n name: 'ui.ask',\n description: 'Ask the user a multiple-choice question in the Agent panel and wait for the answer. Use it when a choice is theirs to make (which bot, which provider, whether to overwrite). Returns { choice, label } or { other } when they type their own answer.',\n risk: 'read',\n client: true,\n input: z.object({\n question: z.string().min(1).max(300),\n options: z.array(z.object({\n label: z.string().min(1).max(80),\n description: z.string().max(200).optional(),\n })).min(2).max(6),\n recommended: z.number().int().min(0).optional().describe('Index of the option you recommend'),\n allowOther: z.boolean().optional().describe('Let the user type their own answer'),\n }),\n })\n\n yield actions.define({\n name: 'system.info',\n description: 'Server address and runtime versions. The address is what NapCat should use in reverse mode.',\n risk: 'read',\n input: z.object({}),\n handler: () => ({\n baseUrl: this.home.server.baseUrl,\n node: process.version,\n platform: process.platform,\n uptime: Math.round(process.uptime()),\n }),\n })\n\n yield actions.define({\n name: 'logs.recent',\n description: 'Recent log lines of Mushin.js itself, oldest first, each with a source tag. Lines written inside a bot are in bots.logs instead.',\n risk: 'read',\n input: z.object({\n limit: z.number().int().min(1).max(1000).optional(),\n tag: z.enum(LOG_TAGS).optional().describe('Only lines from this source: plugin, bot, adapter, flow, provider, agent, system or other'),\n }),\n handler: ({ limit, tag }) => this.home.logger.buffer\n .filter((message) => !this.isBotLog(message) && (!tag || logTag(message.name) === tag))\n .slice(-(limit ?? 200))\n .map(formatLog),\n })\n }\n}\n\nasync function readBody(req: { text(): Promise<string> }) {\n const text = await req.text()\n if (!text) return undefined\n try {\n return JSON.parse(text)\n } catch {\n throw new MushinError('invalid_input', 'Request body must be JSON')\n }\n}\n\nexport default Console\n"],"mappings":"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"}
|
package/dist/static.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { dirname, extname, join, normalize, sep } from "node:path";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
// src/static.ts
|
|
5
|
+
const types = {
|
|
6
|
+
".html": "text/html; charset=utf-8",
|
|
7
|
+
".js": "text/javascript; charset=utf-8",
|
|
8
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
9
|
+
".css": "text/css; charset=utf-8",
|
|
10
|
+
".json": "application/json",
|
|
11
|
+
".svg": "image/svg+xml",
|
|
12
|
+
".png": "image/png",
|
|
13
|
+
".jpg": "image/jpeg",
|
|
14
|
+
".webp": "image/webp",
|
|
15
|
+
".ico": "image/x-icon",
|
|
16
|
+
".woff2": "font/woff2",
|
|
17
|
+
".woff": "font/woff",
|
|
18
|
+
".txt": "text/plain; charset=utf-8",
|
|
19
|
+
".map": "application/json"
|
|
20
|
+
};
|
|
21
|
+
function dashboardRoot() {
|
|
22
|
+
try {
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
return join(dirname(require.resolve("@mushinjs/dashboard/package.json")), "dist");
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Resolves a request path to a file under `root`; unknown paths fall back to index.html for the SPA router. */
|
|
30
|
+
async function readStatic(root, path) {
|
|
31
|
+
let decoded;
|
|
32
|
+
try {
|
|
33
|
+
decoded = decodeURIComponent(path);
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const relative = normalize(decoded).replace(/^([/\\])+/, "");
|
|
38
|
+
const file = join(root, relative);
|
|
39
|
+
if (file !== root && !file.startsWith(root + sep)) return;
|
|
40
|
+
try {
|
|
41
|
+
if ((await stat(file)).isFile()) return {
|
|
42
|
+
body: await readFile(file),
|
|
43
|
+
type: types[extname(file)] ?? "application/octet-stream",
|
|
44
|
+
immutable: relative.startsWith("assets" + sep) || relative.startsWith("assets/")
|
|
45
|
+
};
|
|
46
|
+
} catch {}
|
|
47
|
+
if (extname(relative)) return;
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
body: await readFile(join(root, "index.html")),
|
|
51
|
+
type: types[".html"],
|
|
52
|
+
immutable: false
|
|
53
|
+
};
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
const MISSING_BUILD_PAGE = `<!doctype html>
|
|
57
|
+
<html lang="en"><head><meta charset="utf-8"><title>Mushin.js</title>
|
|
58
|
+
<style>body{font-family:system-ui,sans-serif;max-width:560px;margin:80px auto;padding:0 16px;line-height:1.6;color:#222}code{background:#f2f2f2;padding:2px 6px;border-radius:4px}</style>
|
|
59
|
+
</head><body><h1>Mushin.js is running</h1>
|
|
60
|
+
<p>The dashboard has not been built yet. Run <code>pnpm build</code> in the Mushin.js folder, or start the dev server with <code>pnpm dev</code>.</p>
|
|
61
|
+
</body></html>`;
|
|
62
|
+
export { MISSING_BUILD_PAGE, dashboardRoot, readStatic };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","names":[],"sources":["../src/static.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { dirname, extname, join, normalize, sep } from 'node:path'\n\nconst types: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.woff2': 'font/woff2',\n '.woff': 'font/woff',\n '.txt': 'text/plain; charset=utf-8',\n '.map': 'application/json',\n}\n\nexport function dashboardRoot() {\n try {\n const require = createRequire(import.meta.url)\n return join(dirname(require.resolve('@mushinjs/dashboard/package.json')), 'dist')\n } catch {\n return undefined\n }\n}\n\nexport interface StaticFile {\n body: Buffer\n type: string\n immutable: boolean\n}\n\n/** Resolves a request path to a file under `root`; unknown paths fall back to index.html for the SPA router. */\nexport async function readStatic(root: string, path: string): Promise<StaticFile | undefined> {\n let decoded: string\n try {\n decoded = decodeURIComponent(path)\n } catch {\n return\n }\n const relative = normalize(decoded).replace(/^([/\\\\])+/, '')\n const file = join(root, relative)\n if (file !== root && !file.startsWith(root + sep)) return\n try {\n if ((await stat(file)).isFile()) {\n return {\n body: await readFile(file),\n type: types[extname(file)] ?? 'application/octet-stream',\n immutable: relative.startsWith('assets' + sep) || relative.startsWith('assets/'),\n }\n }\n } catch {}\n if (extname(relative)) return\n try {\n return { body: await readFile(join(root, 'index.html')), type: types['.html'], immutable: false }\n } catch {}\n}\n\nexport const MISSING_BUILD_PAGE = `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><title>Mushin.js</title>\n<style>body{font-family:system-ui,sans-serif;max-width:560px;margin:80px auto;padding:0 16px;line-height:1.6;color:#222}code{background:#f2f2f2;padding:2px 6px;border-radius:4px}</style>\n</head><body><h1>Mushin.js is running</h1>\n<p>The dashboard has not been built yet. Run <code>pnpm build</code> in the Mushin.js folder, or start the dev server with <code>pnpm dev</code>.</p>\n</body></html>`\n"],"mappings":";;;;AAIA,MAAM,QAAgC;CACpC,SAAS;CACT,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,UAAU;CACV,SAAS;CACT,QAAQ;CACR,QAAQ;AACV;AAEA,SAAgB,gBAAgB;CAC9B,IAAI;EACF,MAAM,UAAU,cAAc,YAAY,GAAG;EAC7C,OAAO,KAAK,QAAQ,QAAQ,QAAQ,kCAAkC,CAAC,GAAG,MAAM;CAClF,QAAQ;EACN;CACF;AACF;;AASA,eAAsB,WAAW,MAAc,MAA+C;CAC5F,IAAI;CACJ,IAAI;EACF,UAAU,mBAAmB,IAAI;CACnC,QAAQ;EACN;CACF;CACA,MAAM,WAAW,UAAU,OAAO,CAAC,CAAC,QAAQ,aAAa,EAAE;CAC3D,MAAM,OAAO,KAAK,MAAM,QAAQ;CAChC,IAAI,SAAS,QAAQ,CAAC,KAAK,WAAW,OAAO,GAAG,GAAG;CACnD,IAAI;EACF,KAAK,MAAM,KAAK,IAAI,EAAA,CAAG,OAAO,GAC5B,OAAO;GACL,MAAM,MAAM,SAAS,IAAI;GACzB,MAAM,MAAM,QAAQ,IAAI,MAAM;GAC9B,WAAW,SAAS,WAAW,WAAW,GAAG,KAAK,SAAS,WAAW,SAAS;EACjF;CAEJ,QAAQ,CAAC;CACT,IAAI,QAAQ,QAAQ,GAAG;CACvB,IAAI;EACF,OAAO;GAAE,MAAM,MAAM,SAAS,KAAK,MAAM,YAAY,CAAC;GAAG,MAAM,MAAM;GAAU,WAAW;EAAM;CAClG,QAAQ,CAAC;AACX;AAEA,MAAa,qBAAqB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushinjs/plugin-console",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0-next.20261113103537",
|
|
4
|
+
"description": "HTTP API, WebSocket and dashboard hosting",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mushinjs/mushin.git",
|
|
10
|
+
"directory": "packages/plugin-console"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mushin-plugin"
|
|
27
|
+
],
|
|
28
|
+
"mushin": {
|
|
29
|
+
"kind": "app",
|
|
30
|
+
"essential": true,
|
|
31
|
+
"displayName": {
|
|
32
|
+
"en": "Console",
|
|
33
|
+
"zh-CN": "控制台"
|
|
34
|
+
},
|
|
35
|
+
"description": {
|
|
36
|
+
"en": "Serves the dashboard and its API",
|
|
37
|
+
"zh-CN": "提供控制台页面及其 API"
|
|
38
|
+
},
|
|
39
|
+
"provides": [
|
|
40
|
+
"console"
|
|
41
|
+
],
|
|
42
|
+
"requires": {
|
|
43
|
+
"plugins": {
|
|
44
|
+
"@cordisjs/plugin-server": "*",
|
|
45
|
+
"@mushinjs/plugin-storage": "*"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"optional": {
|
|
49
|
+
"plugins": {
|
|
50
|
+
"@mushinjs/plugin-bots": "*",
|
|
51
|
+
"@mushinjs/plugin-agent": "*",
|
|
52
|
+
"@mushinjs/plugin-providers": "*",
|
|
53
|
+
"@mushinjs/plugin-flows": "*",
|
|
54
|
+
"@mushinjs/plugin-stats": "*"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@mushinjs/core": "0.2.0-next.20261113103537",
|
|
60
|
+
"@mushinjs/plugin-storage": "0.2.0-next.20261113103537",
|
|
61
|
+
"@mushinjs/plugin-bots": "0.2.0-next.20261113103537",
|
|
62
|
+
"@mushinjs/plugin-agent": "0.2.0-next.20261113103537",
|
|
63
|
+
"@cordisjs/plugin-server": "1.7.0",
|
|
64
|
+
"ws": "^8.22.0",
|
|
65
|
+
"@mushinjs/dashboard": "0.2.0-next.20261113103537",
|
|
66
|
+
"@mushinjs/plugin-providers": "0.2.0-next.20261113103537",
|
|
67
|
+
"@mushinjs/plugin-flows": "0.2.0-next.20261113103537",
|
|
68
|
+
"@mushinjs/plugin-stats": "0.2.0-next.20261113103537"
|
|
69
|
+
}
|
|
6
70
|
}
|
package/README.md
DELETED