@starpak/os 0.1.2 → 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/lib/index.d.ts CHANGED
@@ -1,4 +1,78 @@
1
+ import type { Readable } from 'node:stream';
1
2
  import type { ExtensionContext } from '@starpak/iwant';
3
+ /** spak2os 贡献的 APPS(OS 只读消费,不反向写入)。 */
4
+ export interface OsBridgeApp {
5
+ id: string;
6
+ name: string;
7
+ version?: string;
8
+ description?: string;
9
+ web?: string;
10
+ icon?: string;
11
+ width?: number;
12
+ height?: number;
13
+ accent?: string;
14
+ source?: 'extension' | 'mod';
15
+ contributor?: string;
16
+ }
17
+ /** `@starpak/spak2os` 暴露给 OS 的只读能力(结构兼容,避免硬依赖)。 */
18
+ export interface OsBridge {
19
+ readonly readOnly?: boolean;
20
+ listApps(options?: {
21
+ refresh?: boolean;
22
+ }): Promise<OsBridgeApp[]>;
23
+ readAppAsset(id: string, relative: string): Promise<{
24
+ data: Uint8Array;
25
+ mime: string;
26
+ size?: number;
27
+ } | null>;
28
+ info(): Promise<Record<string, unknown>>;
29
+ scan(options: {
30
+ roots: string[];
31
+ kinds?: string[];
32
+ maxDepth?: number;
33
+ limit?: number;
34
+ }): Promise<{
35
+ roots: string[];
36
+ files: Array<{
37
+ path: string;
38
+ name: string;
39
+ ext: string;
40
+ kind: string;
41
+ size: number;
42
+ mtimeMs: number;
43
+ root: string;
44
+ }>;
45
+ scannedDirs: number;
46
+ truncated: boolean;
47
+ errors: Array<{
48
+ path: string;
49
+ message: string;
50
+ }>;
51
+ }>;
52
+ readdir(path: string): Promise<Array<{
53
+ name: string;
54
+ path: string;
55
+ kind: string;
56
+ size?: number;
57
+ mtimeMs?: number;
58
+ }>>;
59
+ stat(path: string): Promise<{
60
+ path: string;
61
+ size: number;
62
+ mtimeMs: number;
63
+ isFile: boolean;
64
+ isDirectory: boolean;
65
+ }>;
66
+ createReadStream(path: string, options?: {
67
+ start?: number;
68
+ end?: number;
69
+ }): Promise<{
70
+ stream: Readable;
71
+ size: number;
72
+ start: number;
73
+ end: number;
74
+ }>;
75
+ }
2
76
  export interface OsOptions {
3
77
  /** 工程根目录(默认当前目录)。 */
4
78
  root?: string;
@@ -8,6 +82,8 @@ export interface OsOptions {
8
82
  port?: number;
9
83
  /** 监听地址(默认 127.0.0.1)。 */
10
84
  host?: string;
85
+ /** spak2os 只读桥;未安装时为 undefined,OS 仍可独立运行。 */
86
+ bridge?: OsBridge;
11
87
  }
12
88
  export interface OsHandle {
13
89
  port: number;
@@ -26,6 +102,7 @@ export declare function runOsCli(options?: {
26
102
  root?: string;
27
103
  port?: number;
28
104
  host?: string;
105
+ bridge?: OsBridge;
29
106
  }): Promise<void>;
30
107
  /** 扩展入口:注册 `spm os` 命令(需要 commands 能力),并顺带激活 apps 扩展。 */
31
108
  export declare function activate(ctx: ExtensionContext): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAYtD,MAAM,WAAW,SAAS;IACxB,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAmDD,wBAAsB,OAAO,CAAC,OAAO,GAAE,SAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqFxE;AAED,iDAAiD;AACjD,wBAAgB,KAAK,CAAC,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,GAAG,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CAevF;AAED,iCAAiC;AACjC,wBAAsB,QAAQ,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3G;AAED,yDAAyD;AACzD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/index.js CHANGED
@@ -20,12 +20,43 @@ const MIME = {
20
20
  '.html': 'text/html; charset=utf-8',
21
21
  '.css': 'text/css; charset=utf-8',
22
22
  '.js': 'text/javascript; charset=utf-8',
23
+ '.mjs': 'text/javascript; charset=utf-8',
23
24
  '.json': 'application/json; charset=utf-8',
24
25
  '.svg': 'image/svg+xml',
25
26
  '.png': 'image/png',
27
+ '.webp': 'image/webp',
28
+ '.jpg': 'image/jpeg',
29
+ '.jpeg': 'image/jpeg',
26
30
  '.ico': 'image/x-icon',
31
+ '.mp3': 'audio/mpeg',
32
+ '.wav': 'audio/wav',
33
+ '.ogg': 'audio/ogg',
34
+ '.m4a': 'audio/mp4',
35
+ '.mp4': 'video/mp4',
36
+ '.webm': 'video/webm',
37
+ '.woff2': 'font/woff2',
38
+ '.woff': 'font/woff',
39
+ '.ttf': 'font/ttf',
27
40
  };
28
- async function listApps(root) {
41
+ /** 桌面壳的安全头:脚本只允许同源文件,禁止被嵌入(防点击劫持)。 */
42
+ const SECURITY_HEADERS = {
43
+ // 样式属性在 UI 里由 innerHTML 注入,只能对 style 放开 unsafe-inline;
44
+ // script 保持 'self',即使清单里有恶意 JS 也不会执行。
45
+ 'content-security-policy': "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: data:; connect-src 'self' blob: data:; frame-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
46
+ 'x-content-type-options': 'nosniff',
47
+ 'referrer-policy': 'no-referrer',
48
+ };
49
+ /**
50
+ * Web 应用(清单里有 `web`)的静态资源安全头。
51
+ * 它们要作为 iframe 嵌进桌面,因此 frame-ancestors 只能是同源 'self';
52
+ * 媒体(本地导入的 blob:)与内联样式在应用里是正常需求。
53
+ */
54
+ const APP_HEADERS = {
55
+ 'content-security-policy': "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: data:; connect-src 'self' blob: data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'",
56
+ 'x-content-type-options': 'nosniff',
57
+ 'referrer-policy': 'no-referrer',
58
+ };
59
+ async function listApps(root, bridge, refresh = false) {
29
60
  const appsDir = (0, node_path_1.join)(root, 'apps');
30
61
  const apps = [];
31
62
  let names = [];
@@ -33,7 +64,7 @@ async function listApps(root) {
33
64
  names = await (0, promises_1.readdir)(appsDir);
34
65
  }
35
66
  catch {
36
- return apps;
67
+ // 没有 apps/ 时仍然可以列出扩展贡献的 APPS
37
68
  }
38
69
  for (const name of names.sort()) {
39
70
  if (name.startsWith('.'))
@@ -47,7 +78,34 @@ async function listApps(root) {
47
78
  // 不是应用目录就跳过
48
79
  }
49
80
  }
50
- return apps;
81
+ if (bridge) {
82
+ try {
83
+ const contributed = await bridge.listApps({ refresh });
84
+ const seen = new Set(apps.map(app => String(app.id ?? '')));
85
+ for (const app of contributed) {
86
+ if (!app?.id || seen.has(app.id))
87
+ continue;
88
+ seen.add(app.id);
89
+ apps.push({
90
+ id: app.id,
91
+ name: app.name,
92
+ version: app.version,
93
+ description: app.description,
94
+ web: app.web || `/apps/${app.id}/`,
95
+ icon: app.icon,
96
+ width: app.width,
97
+ height: app.height,
98
+ accent: app.accent,
99
+ source: app.source ?? 'extension',
100
+ contributor: app.contributor,
101
+ });
102
+ }
103
+ }
104
+ catch (error) {
105
+ console.error('[spak-os] spak2os listApps 失败:', error.message);
106
+ }
107
+ }
108
+ return apps.sort((a, b) => String(a.id).localeCompare(String(b.id)));
51
109
  }
52
110
  async function listMods(root) {
53
111
  const modsDir = (0, node_path_1.join)(root, 'mods');
@@ -77,17 +135,96 @@ function json(response, status, body) {
77
135
  response.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'content-length': Buffer.byteLength(text) });
78
136
  response.end(text);
79
137
  }
138
+ /**
139
+ * Web 应用的静态资源:/apps/<id>/... → apps/<id>/public/...
140
+ * 只暴露 public/,并拒绝任何路径穿越;没有清单或没有 web 字段一律当普通 404。
141
+ */
142
+ async function serveApp(root, url, response, bridge) {
143
+ const match = /^\/apps\/([a-z0-9][a-z0-9._-]*)(\/.*)?$/.exec(url.pathname);
144
+ if (!match)
145
+ return json(response, 404, { error: 'not_found' });
146
+ const [, id, tail] = match;
147
+ // 没有尾斜杠时先重定向,保证页面里的 ./ 相对路径解析到应用目录
148
+ if (!tail) {
149
+ response.writeHead(308, { location: `/apps/${id}/`, 'content-length': 0 });
150
+ response.end();
151
+ return;
152
+ }
153
+ let relative;
154
+ try {
155
+ relative = decodeURIComponent(tail.slice(1)) || 'index.html';
156
+ }
157
+ catch {
158
+ return json(response, 400, { error: 'bad_request' });
159
+ }
160
+ // 1) 工程内 apps/<id>/public/(兼容旧的本地应用)
161
+ let manifest = {};
162
+ try {
163
+ manifest = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(root, 'apps', id, 'spak.app.json'), 'utf-8'));
164
+ }
165
+ catch { /* 扩展贡献的 APPS 没有本地清单 */ }
166
+ if (typeof manifest.web === 'string' && manifest.web) {
167
+ const publicDir = (0, node_path_1.resolve)(root, 'apps', id, 'public');
168
+ const target = (0, node_path_1.resolve)(publicDir, (0, node_path_1.normalize)(relative));
169
+ if (target === publicDir || target.startsWith(publicDir + node_path_1.sep)) {
170
+ let file = target;
171
+ try {
172
+ const info = await (0, promises_1.stat)(file);
173
+ if (info.isDirectory())
174
+ file = (0, node_path_1.join)(file, 'index.html');
175
+ }
176
+ catch {
177
+ return json(response, 404, { error: 'not_found' });
178
+ }
179
+ const data = await (0, promises_1.readFile)(file);
180
+ response.writeHead(200, {
181
+ ...APP_HEADERS,
182
+ 'content-type': MIME[(0, node_path_1.extname)(file)] ?? 'application/octet-stream',
183
+ 'content-length': data.length,
184
+ 'cache-control': 'no-cache',
185
+ });
186
+ response.end(data);
187
+ return;
188
+ }
189
+ }
190
+ // 2) spak2os 只读桥:扩展 / 模组贡献的 APPS
191
+ if (bridge) {
192
+ try {
193
+ const asset = await bridge.readAppAsset(id, relative);
194
+ if (asset) {
195
+ response.writeHead(200, {
196
+ ...APP_HEADERS,
197
+ 'content-type': asset.mime,
198
+ 'content-length': asset.data.length,
199
+ 'cache-control': 'no-cache',
200
+ });
201
+ response.end(Buffer.from(asset.data));
202
+ return;
203
+ }
204
+ }
205
+ catch (error) {
206
+ console.error('[spak-os] spak2os readAppAsset 失败:', error.message);
207
+ }
208
+ }
209
+ return json(response, 404, { error: 'not_found' });
210
+ }
80
211
  async function startOs(options = {}) {
81
212
  const root = (0, node_path_1.resolve)(options.root || process.cwd());
82
213
  // UI 资源随包发布(packages/os/ui),宿主只提供端口与应用目录
83
214
  const uiDir = options.uiDir ? (0, node_path_1.resolve)(options.uiDir) : (0, node_path_1.join)(__dirname, '..', 'ui');
84
215
  const port = options.port ?? 8008;
85
216
  const host = options.host ?? '127.0.0.1';
217
+ const bridge = options.bridge;
86
218
  const server = (0, node_http_1.createServer)(async (request, response) => {
87
219
  try {
88
220
  const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
89
221
  if (url.pathname === '/api/apps') {
90
- return json(response, 200, { apps: await listApps(root) });
222
+ const refresh = url.searchParams.get('refresh') === '1';
223
+ return json(response, 200, {
224
+ apps: await listApps(root, bridge, refresh),
225
+ bridge: Boolean(bridge),
226
+ readOnly: true,
227
+ });
91
228
  }
92
229
  if (url.pathname === '/api/mods') {
93
230
  return json(response, 200, { mods: await listMods(root) });
@@ -95,6 +232,93 @@ async function startOs(options = {}) {
95
232
  if (url.pathname === '/api/health') {
96
233
  return json(response, 200, { status: 'ok', os: 'spak-os', uptime: Math.round(process.uptime()) });
97
234
  }
235
+ // spak2os:只读 API(文件 / 目录池扫描 / 桥信息)
236
+ if (url.pathname.startsWith('/api/spak2os/')) {
237
+ if (!bridge) {
238
+ return json(response, 501, {
239
+ error: 'spak2os_not_installed',
240
+ hint: '先运行 spm iwant -i spak2os',
241
+ });
242
+ }
243
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
244
+ return json(response, 405, { error: 'read_only', hint: 'spak2os 不提供写接口' });
245
+ }
246
+ const action = url.pathname.slice('/api/spak2os/'.length);
247
+ if (action === 'info') {
248
+ return json(response, 200, await bridge.info());
249
+ }
250
+ if (action === 'scan') {
251
+ const roots = url.searchParams.getAll('root').filter(Boolean);
252
+ const kindParam = url.searchParams.get('kind');
253
+ const kinds = kindParam ? kindParam.split(',').map(item => item.trim()).filter(Boolean) : undefined;
254
+ const limit = Number(url.searchParams.get('limit')) || undefined;
255
+ const maxDepth = Number(url.searchParams.get('maxDepth')) || undefined;
256
+ return json(response, 200, await bridge.scan({ roots, kinds, limit, maxDepth }));
257
+ }
258
+ if (action === 'dirs') {
259
+ const info = await bridge.info();
260
+ const target = url.searchParams.get('path') || String(info.home || '/');
261
+ return json(response, 200, {
262
+ path: target,
263
+ entries: await bridge.readdir(target),
264
+ });
265
+ }
266
+ if (action === 'stat') {
267
+ const target = url.searchParams.get('path');
268
+ if (!target)
269
+ return json(response, 400, { error: 'missing_path' });
270
+ return json(response, 200, await bridge.stat(target));
271
+ }
272
+ if (action === 'file') {
273
+ const target = url.searchParams.get('path');
274
+ if (!target)
275
+ return json(response, 400, { error: 'missing_path' });
276
+ const info = await bridge.stat(target);
277
+ if (!info.isFile)
278
+ return json(response, 404, { error: 'not_a_file' });
279
+ let start = 0;
280
+ let end = info.size - 1;
281
+ let status = 200;
282
+ const range = request.headers.range;
283
+ if (range) {
284
+ const parsed = /^bytes=(\d*)-(\d*)$/.exec(range.trim());
285
+ if (!parsed) {
286
+ response.writeHead(416, { 'content-range': `bytes */${info.size}` });
287
+ return response.end();
288
+ }
289
+ const [, from, to] = parsed;
290
+ if (from)
291
+ start = Number(from);
292
+ else if (to)
293
+ start = Math.max(0, info.size - Number(to));
294
+ if (to && from)
295
+ end = Math.min(info.size - 1, Number(to));
296
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end || start >= info.size) {
297
+ response.writeHead(416, { 'content-range': `bytes */${info.size}` });
298
+ return response.end();
299
+ }
300
+ status = 206;
301
+ }
302
+ const slice = await bridge.createReadStream(target, { start, end });
303
+ const headers = {
304
+ 'content-type': MIME[(0, node_path_1.extname)(target)] ?? 'application/octet-stream',
305
+ 'content-length': slice.end - slice.start + 1,
306
+ 'accept-ranges': 'bytes',
307
+ 'cache-control': 'no-store',
308
+ 'x-content-type-options': 'nosniff',
309
+ };
310
+ if (status === 206)
311
+ headers['content-range'] = `bytes ${slice.start}-${slice.end}/${slice.size}`;
312
+ response.writeHead(status, headers);
313
+ slice.stream.on('error', () => response.destroy());
314
+ return slice.stream.pipe(response);
315
+ }
316
+ return json(response, 404, { error: 'not_found' });
317
+ }
318
+ // Web 应用静态资源(apps/<id>/public/)+ 同源 iframe 安全头
319
+ if (url.pathname.startsWith('/apps/')) {
320
+ return await serveApp(root, url, response, bridge);
321
+ }
98
322
  // 静态文件(禁止路径穿越)
99
323
  const relative = (0, node_path_1.normalize)(decodeURIComponent(url.pathname)).replace(/^([/\\])+/, '');
100
324
  const target = (0, node_path_1.resolve)(uiDir, relative || 'index.html');
@@ -114,6 +338,7 @@ async function startOs(options = {}) {
114
338
  }
115
339
  const data = await (0, promises_1.readFile)(file);
116
340
  response.writeHead(200, {
341
+ ...SECURITY_HEADERS,
117
342
  'content-type': MIME[(0, node_path_1.extname)(file)] ?? 'application/octet-stream',
118
343
  'content-length': data.length,
119
344
  'cache-control': 'no-cache',
@@ -121,7 +346,9 @@ async function startOs(options = {}) {
121
346
  response.end(data);
122
347
  }
123
348
  catch (error) {
124
- json(response, 500, { error: 'internal_error', message: error.message });
349
+ // 只写服务端日志,响应不带内部错误与绝对路径
350
+ console.error('[spak-os]', error.message);
351
+ json(response, 500, { error: 'internal_error' });
125
352
  }
126
353
  });
127
354
  await new Promise((resolveListen, rejectListen) => {
@@ -235,7 +462,12 @@ async function activate(ctx) {
235
462
  if (!Number.isInteger(port) || port < 0 || port > 65535) {
236
463
  throw new error_1.SpakError(error_1.CODES.INVALID_OPTION, { message: `端口不合法:${parsed.port}(0-65535)` });
237
464
  }
238
- await runOsCli({ port, host: parsed.host, root: parsed.root });
465
+ // 所有扩展此时都已激活(包括 thismv 代激活的 spak2os),再取只读桥。
466
+ const bridge = ctx.getApi?.('spak2os');
467
+ if (!bridge) {
468
+ ctx.logger.warn('未检测到 spak2os:扩展 APPS 与外部媒体扫描不可用(spm iwant -i spak2os,或 iwant -i thismv)');
469
+ }
470
+ await runOsCli({ port, host: parsed.host, root: parsed.root, bridge });
239
471
  },
240
472
  });
241
473
  // spakos 的入口是 @starpak/os;应用管理 provider 在 @starpak/apps 里,
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,2CAA2C;AAC3C,EAAE;AACF,gCAAgC;AAChC,uBAAuB;AACvB,mDAAmD;AACnD,EAAE;AACF,qDAAqD;;AAqQrD,0BAmLC;AAGD,sBAeC;AAGD,4BAWC;AAGD,4BA6DC;AAthBD,yCAAmF;AACnF,+CAA0D;AAC1D,6CAA2C;AAC3C,yCAAkE;AAElE,0CAAiD;AAyCjD,MAAM,IAAI,GAA2B;IACnC,OAAO,EAAE,0BAA0B;IACnC,MAAM,EAAE,yBAAyB;IACjC,KAAK,EAAE,gCAAgC;IACvC,MAAM,EAAE,gCAAgC;IACxC,OAAO,EAAE,iCAAiC;IAC1C,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,cAAc;IACtB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,YAAY;IACtB,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;CACnB,CAAA;AAED,sCAAsC;AACtC,MAAM,gBAAgB,GAA2B;IAC/C,uDAAuD;IACvD,sCAAsC;IACtC,yBAAyB,EAAE,iPAAiP;IAC5Q,wBAAwB,EAAE,SAAS;IACnC,iBAAiB,EAAE,aAAa;CACjC,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,GAA2B;IAC1C,yBAAyB,EAAE,+QAA+Q;IAC1S,wBAAwB,EAAE,SAAS;IACnC,iBAAiB,EAAE,aAAa;CACjC,CAAA;AAsBD,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,MAAiB,EAAE,OAAO,GAAG,KAAK;IACtE,MAAM,OAAO,GAAG,IAAA,gBAAI,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAClC,MAAM,IAAI,GAAmC,EAAE,CAAA;IAC/C,IAAI,KAAK,GAAa,EAAE,CAAA;IACxB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAA,kBAAO,EAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QAClC,MAAM,YAAY,GAAG,IAAA,gBAAI,EAAC,OAAO,EAAE,IAAI,EAAE,eAAe,CAAC,CAAA;QACzD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAA,mBAAQ,EAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;YAClE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,IAAI,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAA;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;YACtD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;YAC3D,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;gBAC9B,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAQ;gBAC1C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBAChB,IAAI,CAAC,IAAI,CAAC;oBACR,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,OAAO,EAAE,GAAG,CAAC,OAAO;oBACpB,WAAW,EAAE,GAAG,CAAC,WAAW;oBAC5B,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,SAAS,GAAG,CAAC,EAAE,GAAG;oBAClC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,WAAW;oBACjC,WAAW,EAAE,GAAG,CAAC,WAAW;iBAC7B,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAG,KAAe,CAAC,OAAO,CAAC,CAAA;QAC3E,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AACtE,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,MAAM,OAAO,GAAG,IAAA,gBAAI,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAClC,MAAM,IAAI,GAAmC,EAAE,CAAA;IAC/C,IAAI,KAAK,GAAa,EAAE,CAAA;IACxB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAA,kBAAO,EAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QAClC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAA,mBAAQ,EAAC,IAAA,gBAAI,EAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;YACrF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,IAAI,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAA;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,IAAI,CAAC,QAAwB,EAAE,MAAc,EAAE,IAAa;IACnE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACjC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC5H,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACpB,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAQ,EAAE,QAAwB,EAAE,MAAiB;IACzF,MAAM,KAAK,GAAG,yCAAyC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC1E,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;IAC9D,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,KAAK,CAAA;IAE1B,mCAAmC;IACnC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAA;QAC1E,QAAQ,CAAC,GAAG,EAAE,CAAA;QACd,OAAM;IACR,CAAC;IAED,IAAI,QAAgB,CAAA;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,YAAY,CAAA;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAA;IACtD,CAAC;IAED,qCAAqC;IACrC,IAAI,QAAQ,GAAqB,EAAE,CAAA;IACnC,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAA,mBAAQ,EAAC,IAAA,gBAAI,EAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;IACzF,CAAC;IAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;IAEnC,IAAI,OAAO,QAAQ,CAAC,GAAG,KAAK,QAAQ,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,IAAA,mBAAO,EAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,IAAA,mBAAO,EAAC,SAAS,EAAE,IAAA,qBAAS,EAAC,QAAQ,CAAC,CAAC,CAAA;QACtD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS,GAAG,eAAG,CAAC,EAAE,CAAC;YAC/D,IAAI,IAAI,GAAG,MAAM,CAAA;YACjB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,IAAA,eAAI,EAAC,IAAI,CAAC,CAAA;gBAC7B,IAAI,IAAI,CAAC,WAAW,EAAE;oBAAE,IAAI,GAAG,IAAA,gBAAI,EAAC,IAAI,EAAE,YAAY,CAAC,CAAA;YACzD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YACpD,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAI,CAAC,CAAA;YACjC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;gBACtB,GAAG,WAAW;gBACd,cAAc,EAAE,IAAI,CAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,CAAC,IAAI,0BAA0B;gBACjE,gBAAgB,EAAE,IAAI,CAAC,MAAM;gBAC7B,eAAe,EAAE,UAAU;aAC5B,CAAC,CAAA;YACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;IACH,CAAC;IAED,iCAAiC;IACjC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;YACrD,IAAI,KAAK,EAAE,CAAC;gBACV,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;oBACtB,GAAG,WAAW;oBACd,cAAc,EAAE,KAAK,CAAC,IAAI;oBAC1B,gBAAgB,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;oBACnC,eAAe,EAAE,UAAU;iBAC5B,CAAC,CAAA;gBACF,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;gBACrC,OAAM;YACR,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAG,KAAe,CAAC,OAAO,CAAC,CAAA;QAC/E,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;AACpD,CAAC;AAEM,KAAK,UAAU,OAAO,CAAC,UAAqB,EAAE;IACnD,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACnD,yCAAyC;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAA,mBAAO,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAA,gBAAI,EAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAA;IACjC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAA;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAE7B,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,KAAK,EAAE,OAAwB,EAAE,QAAwB,EAAE,EAAE;QACvF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC,CAAA;YAExF,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,CAAA;gBACvD,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;oBACzB,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC;oBAC3C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC;oBACvB,QAAQ,EAAE,IAAI;iBACf,CAAC,CAAA;YACJ,CAAC;YACD,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBACjC,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC5D,CAAC;YACD,IAAI,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;YACnG,CAAC;YAED,mCAAmC;YACnC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;wBACzB,KAAK,EAAE,uBAAuB;wBAC9B,IAAI,EAAE,0BAA0B;qBACjC,CAAC,CAAA;gBACJ,CAAC;gBACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;oBAC1D,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAA;gBAC5E,CAAC;gBAED,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;gBACzD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;oBACtB,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;gBACjD,CAAC;gBACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;oBACtB,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;oBAC7D,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;oBAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;oBACnG,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,SAAS,CAAA;oBAChE,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,SAAS,CAAA;oBACtE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAA;gBAClF,CAAC;gBACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;oBACtB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;oBAChC,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAA;oBACvE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;wBACzB,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;qBACtC,CAAC,CAAA;gBACJ,CAAC;gBACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;oBACtB,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;oBAC3C,IAAI,CAAC,MAAM;wBAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;oBAClE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;gBACvD,CAAC;gBACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;oBACtB,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;oBAC3C,IAAI,CAAC,MAAM;wBAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;oBAClE,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBACtC,IAAI,CAAC,IAAI,CAAC,MAAM;wBAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;oBAErE,IAAI,KAAK,GAAG,CAAC,CAAA;oBACb,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;oBACvB,IAAI,MAAM,GAAG,GAAG,CAAA;oBAChB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAA;oBACnC,IAAI,KAAK,EAAE,CAAC;wBACV,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;wBACvD,IAAI,CAAC,MAAM,EAAE,CAAC;4BACZ,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,eAAe,EAAE,WAAW,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;4BACpE,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAA;wBACvB,CAAC;wBACD,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,MAAM,CAAA;wBAC3B,IAAI,IAAI;4BAAE,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;6BACzB,IAAI,EAAE;4BAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;wBACxD,IAAI,EAAE,IAAI,IAAI;4BAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;wBACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;4BAC1F,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,eAAe,EAAE,WAAW,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;4BACpE,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAA;wBACvB,CAAC;wBACD,MAAM,GAAG,GAAG,CAAA;oBACd,CAAC;oBAED,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;oBACnE,MAAM,OAAO,GAAoC;wBAC/C,cAAc,EAAE,IAAI,CAAC,IAAA,mBAAO,EAAC,MAAM,CAAC,CAAC,IAAI,0BAA0B;wBACnE,gBAAgB,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC;wBAC7C,eAAe,EAAE,OAAO;wBACxB,eAAe,EAAE,UAAU;wBAC3B,wBAAwB,EAAE,SAAS;qBACpC,CAAA;oBACD,IAAI,MAAM,KAAK,GAAG;wBAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAA;oBAChG,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;oBACnC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;oBAClD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBACpC,CAAC;gBACD,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YACpD,CAAC;YAED,+CAA+C;YAC/C,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;YACpD,CAAC;YAED,eAAe;YACf,MAAM,QAAQ,GAAG,IAAA,qBAAS,EAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;YACrF,MAAM,MAAM,GAAG,IAAA,mBAAO,EAAC,KAAK,EAAE,QAAQ,IAAI,YAAY,CAAC,CAAA;YACvD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,eAAG,CAAC,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;gBACxD,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YACpD,CAAC;YAED,IAAI,IAAI,GAAG,MAAM,CAAA;YACjB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,IAAA,eAAI,EAAC,IAAI,CAAC,CAAA;gBAC7B,IAAI,IAAI,CAAC,WAAW,EAAE;oBAAE,IAAI,GAAG,IAAA,gBAAI,EAAC,IAAI,EAAE,YAAY,CAAC,CAAA;YACzD,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,IAAA,mBAAO,EAAC,QAAQ,CAAC;oBAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;gBACzE,IAAI,GAAG,IAAA,gBAAI,EAAC,KAAK,EAAE,YAAY,CAAC,CAAA,CAAC,SAAS;YAC5C,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAI,CAAC,CAAA;YACjC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;gBACtB,GAAG,gBAAgB;gBACnB,cAAc,EAAE,IAAI,CAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,CAAC,IAAI,0BAA0B;gBACjE,gBAAgB,EAAE,IAAI,CAAC,MAAM;gBAC7B,eAAe,EAAE,UAAU;aAC5B,CAAC,CAAA;YACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,wBAAwB;YACxB,OAAO,CAAC,KAAK,CAAC,WAAW,EAAG,KAAe,CAAC,OAAO,CAAC,CAAA;YACpD,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAA;QAClD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,IAAI,OAAO,CAAO,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE;QACtD,MAAM,OAAO,GAAG,CAAC,KAA4B,EAAE,EAAE;YAC/C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5B,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,CAAA;YAChC,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAChC,OAAO,YAAY,CAAC,IAAI,iBAAS,CAAC,aAAK,CAAC,WAAW,EAAE;oBACnD,OAAO,EAAE,MAAM,MAAM,OAAO;oBAC5B,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;oBACvB,KAAK,EAAE,KAAK;iBACb,CAAC,CAAC,CAAA;YACL,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBACtD,OAAO,YAAY,CAAC,IAAI,iBAAS,CAAC,aAAK,CAAC,aAAa,EAAE;oBACrD,OAAO,EAAE,UAAU,MAAM,EAAE;oBAC3B,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;oBACvB,KAAK,EAAE,KAAK;iBACb,CAAC,CAAC,CAAA;YACL,CAAC;YACD,YAAY,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC,CAAA;QACD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;YAC7B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5B,aAAa,EAAE,CAAA;QACjB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAA;IAChC,MAAM,UAAU,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;IAE/E,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,IAAI;QACJ,GAAG,EAAE,UAAU,IAAI,IAAI,UAAU,EAAE;QACnC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC;KAC7E,CAAA;AACH,CAAC;AAED,iDAAiD;AACjD,SAAgB,KAAK,CAAC,GAAmC,EAAE,SAAoB,EAAE;IAC/E,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,GAAG,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,sCAAsC,CAAC,CAAA;QAC3D,OAAM;IACR,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE;QAC9D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;KACrD,CAAC,CAAC,CAAA;IACH,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE;QAC9D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;KACrD,CAAC,CAAC,CAAA;AACL,CAAC;AAED,iCAAiC;AAC1B,KAAK,UAAU,QAAQ,CAAC,UAA8E,EAAE;IAC7G,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACrC,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,CAAC,GAAG,EAAE,CAAC,CAAA;IAC5C,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAA,gBAAI,EAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;IACxF,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAC9B,MAAM,IAAI,OAAO,CAAO,CAAC,WAAW,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAA,CAAC,CAAC,CAAA;QACpE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC5B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC/B,CAAC,CAAC,CAAA;IACF,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;AAC5B,CAAC;AAED,yDAAyD;AAClD,KAAK,UAAU,QAAQ,CAAC,GAAqB;IAClD,GAAG,CAAC,eAAe,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,oCAAoC;QACjD,IAAI,EAAE,EAAE;QACR,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,gBAAgB,EAAE;YAC7D,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,iCAAiC,EAAE;YAC1E,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,EAAE;SAC9D;QACD,QAAQ,EAAE,CAAC,QAAQ,EAAE,+BAA+B,CAAC;QACrD,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YAChC,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAEpD,MAAM,MAAM,GAA2B,EAAE,CAAA;YACzC,MAAM,OAAO,GAAa,EAAE,CAAA;YAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;gBACnB,MAAM,IAAI,GAAG,GAAW,EAAE;oBACxB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;oBACxB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;wBAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;wBAAC,OAAO,EAAE,CAAA;oBAAC,CAAC;oBAChF,CAAC,EAAE,CAAA;oBACH,OAAO,IAAI,CAAA;gBACb,CAAC,CAAA;gBACD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,QAAQ;oBAAE,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;qBACrD,IAAI,GAAG,KAAK,QAAQ;oBAAE,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;qBAC1C,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,QAAQ;oBAAE,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;qBAC1D,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC;;oBAC1C,MAAM,IAAI,iBAAS,CAAC,aAAK,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG,EAAE,EAAE,CAAC,CAAA;YACjF,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACnB,MAAM,IAAI,iBAAS,CAAC,aAAK,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,UAAU,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;YAC1F,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YACnE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;gBACxD,MAAM,IAAI,iBAAS,CAAC,aAAK,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,CAAA;YACzF,CAAC;YAED,4CAA4C;YAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAW,SAAS,CAAC,CAAA;YAChD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAA;YAC5F,CAAC;YACD,MAAM,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QACxE,CAAC;KACF,CAAC,CAAA;IAEF,2DAA2D;IAC3D,mCAAmC;IACnC,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAA,2BAAa,EAAC,UAAU,CAAC,CAAA;QAC7C,MAAM,IAAI,GAAG,WAAW,CAAC,eAAe,CAA0D,CAAA;QAClG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAE,KAAe,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,eAAgB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@starpak/os",
3
3
  "description": "Spak OS - desktop shell plugin (window manager, taskbar, launcher) served on :8008",
4
- "version": "0.1.2",
4
+ "version": "0.2.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -11,7 +11,7 @@
11
11
  "author": "Spak Team",
12
12
  "license": "MIT",
13
13
  "dependencies": {
14
- "@starpak/error": "^0.1.5",
15
- "@starpak/iwant": "^0.1.0"
14
+ "@starpak/error": "^0.1.7",
15
+ "@starpak/iwant": "^0.1.2"
16
16
  }
17
17
  }
package/ui/os.css CHANGED
@@ -80,7 +80,7 @@ body {
80
80
  .icon:hover, .icon:focus-visible { background: rgba(255, 255, 255, .08); outline: none; }
81
81
  .icon .glyph {
82
82
  width: 54px; height: 54px; display: grid; place-items: center;
83
- border-radius: 16px; color: #ffcf87;
83
+ border-radius: 16px; color: var(--app-accent, #ffcf87);
84
84
  background: linear-gradient(160deg, rgba(255, 255, 255, .14), rgba(255, 255, 255, .05));
85
85
  border: 1px solid rgba(255, 255, 255, .20);
86
86
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), 0 8px 22px rgba(0, 0, 0, .40);
@@ -116,6 +116,49 @@ body {
116
116
  .win .body p { margin: 0 0 8px; color: var(--dim); }
117
117
  .win .body code { color: var(--accent2); }
118
118
 
119
+ /* settings */
120
+ .settings-shell { display: flex; flex-direction: column; gap: 10px; }
121
+ .settings-card { padding: 12px 14px; border-radius: 14px; background: rgba(255, 255, 255, .045); border: 1px solid var(--line); }
122
+ .settings-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
123
+ .settings-head b { display: block; font-size: 13px; }
124
+ .settings-head small { display: block; color: var(--dim); font-size: 10.5px; }
125
+ .settings-btn { height: 32px; padding: 0 12px; border-radius: 10px; border: 1px solid var(--line); background: rgba(255, 255, 255, .06); color: var(--fg); font-size: 11px; cursor: pointer; }
126
+ .settings-btn:hover { border-color: rgba(255, 138, 0, .5); }
127
+ .settings-btn:disabled { opacity: .4; cursor: default; }
128
+ .settings-line { display: flex; align-items: center; gap: 8px; margin-top: 9px; color: var(--dim); font-size: 11.5px; }
129
+ .settings-line .dot.off { background: #777; }
130
+ .settings-help { margin: 8px 0 0; color: var(--dim); font-size: 11px; line-height: 1.6; }
131
+ .settings-roots { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
132
+ .settings-root { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: 9px; background: rgba(255, 255, 255, .05); font: 11px/1.4 ui-monospace, monospace; }
133
+ .settings-root span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
134
+ .settings-root button { width: 24px; height: 24px; border: 0; border-radius: 7px; background: transparent; color: var(--dim); cursor: pointer; }
135
+ .settings-root button:hover { background: rgba(255, 255, 255, .1); color: var(--fg); }
136
+ .settings-empty { padding: 10px; border-radius: 9px; background: rgba(255, 255, 255, .035); color: var(--dim); font-size: 11px; }
137
+ .settings-add { display: flex; gap: 6px; margin-top: 8px; }
138
+ .settings-add input { flex: 1; min-width: 0; height: 32px; padding: 0 10px; border-radius: 9px; border: 1px solid var(--line); background: rgba(255, 255, 255, .05); color: var(--fg); font: 11px/1 ui-monospace, monospace; outline: none; }
139
+ .settings-add input:focus { border-color: rgba(255, 138, 0, .5); }
140
+
141
+ /* web 应用:iframe 铺满,去掉内边距 */
142
+ .win .body.frame-body { padding: 0; overflow: hidden; position: relative; background: #0d0a0c; }
143
+ .frame-body iframe { display: block; width: 100%; height: 100%; border: 0; background: #0d0a0c; }
144
+ .win.web-app { border-radius: 18px; }
145
+ .win.maximized { border-radius: 12px; }
146
+ .win.maximized header { border-radius: 11px 11px 0 0; }
147
+
148
+ /* 8 向缩放热区(全部在窗口内部,避免 overflow 裁剪) */
149
+ .win .rs { position: absolute; z-index: 6; touch-action: none; }
150
+ .rs-n, .rs-s { left: 10px; right: 10px; height: 7px; cursor: ns-resize; }
151
+ .rs-n { top: 0; }
152
+ .rs-s { bottom: 0; }
153
+ .rs-e, .rs-w { top: 10px; bottom: 10px; width: 7px; cursor: ew-resize; }
154
+ .rs-e { right: 0; }
155
+ .rs-w { left: 0; }
156
+ .rs-ne, .rs-nw, .rs-se, .rs-sw { width: 12px; height: 12px; }
157
+ .rs-ne { top: 0; right: 0; cursor: nesw-resize; }
158
+ .rs-nw { top: 0; left: 0; cursor: nwse-resize; }
159
+ .rs-se { bottom: 0; right: 0; cursor: nwse-resize; }
160
+ .rs-sw { bottom: 0; left: 0; cursor: nesw-resize; }
161
+
119
162
  /* terminal */
120
163
  .term { font: 13px/1.6 ui-monospace, "SF Mono", Menlo, monospace; background: #080b14; color: #cfe3ff; }
121
164
  .term .body { padding: 12px; }
@@ -190,7 +233,8 @@ body {
190
233
  color: var(--fg); font: inherit; cursor: pointer; transition: background .15s, border-color .15s;
191
234
  }
192
235
  .tile:hover { border-color: rgba(255, 138, 0, .5); }
193
- .tile .glyph { font-size: 22px; }
236
+ .tile .glyph { font-size: 22px; color: var(--app-accent, #ffcf87); }
237
+ .tile .glyph svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
194
238
 
195
239
  /* touch: 放大热区 */
196
240
  @media (pointer: coarse) {
@@ -214,6 +258,7 @@ body {
214
258
  width: calc(100vw - 16px) !important;
215
259
  height: calc(100dvh - var(--bar-h) - 16px) !important;
216
260
  }
261
+ .win .rs { display: none; }
217
262
  }
218
263
 
219
264
  /* 终端输入:彻底去掉 UA 边框/背景 */
package/ui/os.js CHANGED
@@ -8,15 +8,28 @@
8
8
  const launcher = $('#launcher')
9
9
  const clock = $('#clock')
10
10
 
11
- const state = { z: 10, open: new Map(), seq: 0, focused: null, installed: [], mods: [] }
11
+ const state = { z: 10, open: new Map(), seq: 0, focused: null, installed: [], mods: [], bridge: false }
12
+
13
+ // 清单字段来自磁盘(可被第三方 .app/.pak 控制),进 innerHTML 前一律转义,
14
+ // 防止存储型 XSS 直接拿下桌面壳。
15
+ const esc = (value) => String(value ?? '').replace(/[&<>"']/g, ch => ({
16
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
17
+ }[ch]))
12
18
 
13
19
  const svg = (body) => `<svg viewBox="0 0 24 24" aria-hidden="true">${body}</svg>`
14
- const APPS = {
20
+ const BUILTIN = {
15
21
  files: { title: '文件', glyph: svg('<path d="M3 6h6l2 2h10v10H3z"/>'), mount: mountFiles },
16
22
  terminal: { title: '终端', glyph: svg('<rect x="3" y="4" width="18" height="16" rx="2"/><path d="m7 9 3 3-3 3M13 15h4"/>'), mount: mountTerminal },
17
23
  settings: { title: '设置', glyph: svg('<circle cx="12" cy="12" r="3"/><path d="M12 3v2m0 14v2M3 12h2m14 0h2M5.6 5.6l1.4 1.4m10 10 1.4 1.4m0-12.8-1.4 1.4m-10 10-1.4 1.4"/>'), mount: mountSettings },
18
24
  mods: { title: '模组', glyph: svg('<path d="M4 7h7v7H4zM13 4h7v7h-7zM13 13h7v7h-7zM4 15h7v5H4z"/>'), mount: mountMods },
19
25
  }
26
+ /** 已知扩展 APP 的品牌字形;未登记的用通用窗口图标。 */
27
+ const APP_STYLE = {
28
+ music: { glyph: svg('<path d="M9 18V6l9-2v12"/><circle cx="6.5" cy="18.5" r="2.6"/><circle cx="15.5" cy="16.5" r="2.6"/>'), accent: '#ff8a7a' },
29
+ nv: { glyph: svg('<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m10 9 5 3-5 3z"/><path d="M7 5v14M17 5v14" opacity=".45"/>'), accent: '#8f9dff' },
30
+ }
31
+ // 只放内建窗口;扩展 APPS 一律等 /api/apps(spak2os)确认后再注册。
32
+ const APPS = { ...BUILTIN }
20
33
 
21
34
  /* ---------- 窗口 ---------- */
22
35
  function openApp(id, payload) {
@@ -26,38 +39,185 @@
26
39
  if (state.open.has(key)) return focus(state.open.get(key).el)
27
40
 
28
41
  const el = document.createElement('section')
29
- el.className = `win ${id}${id === 'terminal' ? ' term' : ''}`
42
+ el.className = `win ${id}${id === 'terminal' ? ' term' : ''}${app.web ? ' web-app' : ''}`
30
43
  el.tabIndex = 0
31
44
  el.innerHTML = `
32
- <header><b>${app.title}</b><span class="spacer"></span>
33
- <button data-act="min" title="最小化">–</button>
34
- <button data-act="close" title="关闭">×</button>
45
+ <header><b>${esc(app.title)}</b><span class="spacer"></span>
46
+ <button data-act="min" title="最小化" aria-label="最小化">–</button>
47
+ <button data-act="max" title="全屏(Esc 退出)" aria-label="全屏">⛶</button>
48
+ <button data-act="close" title="关闭" aria-label="关闭">×</button>
35
49
  </header>
36
50
  <div class="body"></div>`
37
51
  const body = $('.body', el)
38
52
 
39
53
  const narrow = desktop.clientWidth < 600
40
- const width = narrow ? desktop.clientWidth - 16 : Math.min(560, Math.max(300, desktop.clientWidth - 60))
41
- const height = narrow ? desktop.clientHeight - 16 : Math.min(380, Math.max(180, desktop.clientHeight - 80))
54
+ const pref = app.size || { w: 560, h: 380 }
55
+ const width = narrow ? desktop.clientWidth - 16 : Math.min(pref.w, desktop.clientWidth - 24)
56
+ const height = narrow ? desktop.clientHeight - 16 : Math.min(pref.h, desktop.clientHeight - 24)
42
57
  const n = state.seq++
58
+ const left = narrow ? 8 : Math.max(8, Math.min((desktop.clientWidth - width) / 2 + n * 18, desktop.clientWidth - width - 8))
59
+ const top = narrow ? 8 : Math.max(8, Math.min((desktop.clientHeight - height) / 2 + n * 14, desktop.clientHeight - height - 8))
43
60
  el.style.width = width + 'px'
44
61
  el.style.height = height + 'px'
45
- el.style.left = narrow ? '8px' : Math.min(150 + n * 26, Math.max(10, desktop.clientWidth - width - 10)) + 'px'
46
- el.style.top = narrow ? '8px' : Math.min(40 + n * 22, Math.max(10, desktop.clientHeight - height - 10)) + 'px'
62
+ el.style.left = left + 'px'
63
+ el.style.top = top + 'px'
64
+ el.dataset.minW = String(app.web ? 560 : 280)
65
+ el.dataset.minH = String(app.web ? 360 : 160)
47
66
 
48
67
  layer.appendChild(el)
49
- const rec = { el, id, key, body, min: false }
68
+ const rec = { el, id, key, body, min: false, max: false, restore: null }
50
69
  state.open.set(key, rec)
51
70
  bindDrag(el, $('header', el))
71
+ bindResize(el)
52
72
  bindTask(rec)
53
- app.mount(body, payload, el)
73
+ if (app.web) mountFrame(body, app.web, app.title)
74
+ else app.mount(body, payload, el)
54
75
  focus(el)
55
76
  el.addEventListener('pointerdown', () => focus(el))
56
77
  $('[data-act="close"]', el).onclick = () => close(rec)
57
78
  $('[data-act="min"]', el).onclick = () => minimize(rec)
79
+ $('[data-act="max"]', el).onclick = () => toggleFullscreen(rec)
80
+ $('header', el).addEventListener('dblclick', (event) => {
81
+ if (!event.target.closest('button')) toggleFullscreen(rec)
82
+ })
58
83
  return rec
59
84
  }
60
85
 
86
+ /** 浏览器级全屏:隐藏浏览器 UI、OS 任务栏与窗口边框。 */
87
+ async function toggleFullscreen(rec) {
88
+ if (document.fullscreenElement) {
89
+ try { await document.exitFullscreen() } catch { /* ignore */ }
90
+ return
91
+ }
92
+ if (!rec.el.querySelector('iframe') && !rec.el.requestFullscreen) {
93
+ toggleMax(rec)
94
+ return
95
+ }
96
+ const target = rec.el.querySelector('iframe') || rec.el
97
+ try {
98
+ await target.requestFullscreen({ navigationUI: 'hide' })
99
+ toast('全屏 · Esc 退出')
100
+ } catch {
101
+ // Fullscreen API 被拒绝时退回桌面内最大化
102
+ toggleMax(rec)
103
+ toast('浏览器拒绝了全屏,已最大化窗口')
104
+ }
105
+ }
106
+
107
+ document.addEventListener('fullscreenchange', () => {
108
+ const active = document.fullscreenElement
109
+ document.body.classList.toggle('app-fullscreen', Boolean(active))
110
+ for (const rec of state.open.values()) {
111
+ const isOwn = Boolean(active) && (rec.el === active || rec.el.contains(active))
112
+ rec.el.classList.toggle('fullscreen', isOwn)
113
+ const button = $('[data-act="max"]', rec.el)
114
+ if (button) {
115
+ button.textContent = isOwn ? '⤢' : '⛶'
116
+ button.title = isOwn ? '退出全屏(Esc)' : '全屏(Esc 退出)'
117
+ }
118
+ }
119
+ if (!active) {
120
+ // 退出后把焦点还给原来的窗口
121
+ const focused = [...state.open.values()].find(rec => rec.el.classList.contains('focused'))
122
+ if (focused) focusedTask(focused)
123
+ }
124
+ })
125
+
126
+ function focusedTask(rec) {
127
+ rec.el.hidden = false
128
+ rec.min = false
129
+ rec.el.classList.remove('minimizing')
130
+ focus(rec.el)
131
+ renderTasks()
132
+ }
133
+
134
+ function mountFrame(body, src, title) {
135
+ body.classList.add('frame-body')
136
+ const frame = document.createElement('iframe')
137
+ frame.src = src
138
+ frame.title = title
139
+ frame.loading = 'eager'
140
+ frame.setAttribute('allow', 'autoplay; fullscreen; encrypted-media; picture-in-picture')
141
+ frame.setAttribute('allowfullscreen', '')
142
+ body.appendChild(frame)
143
+ }
144
+
145
+ function toggleMax(rec) {
146
+ const el = rec.el
147
+ if (rec.max) {
148
+ Object.assign(el.style, rec.restore)
149
+ rec.max = false
150
+ el.classList.remove('maximized')
151
+ $('[data-act="max"]', el).textContent = '□'
152
+ $('[data-act="max"]', el).title = '最大化'
153
+ } else {
154
+ rec.restore = { left: el.style.left, top: el.style.top, width: el.style.width, height: el.style.height }
155
+ el.style.left = '8px'
156
+ el.style.top = '8px'
157
+ el.style.width = (desktop.clientWidth - 16) + 'px'
158
+ el.style.height = (desktop.clientHeight - 16) + 'px'
159
+ rec.max = true
160
+ el.classList.add('maximized')
161
+ $('[data-act="max"]', el).textContent = '❐'
162
+ $('[data-act="max"]', el).title = '还原'
163
+ }
164
+ focus(el)
165
+ }
166
+
167
+ /** 8 向缩放;窗口最大化或窄屏时禁用。 */
168
+ function bindResize(el) {
169
+ const dirs = ['n', 'e', 's', 'w', 'ne', 'nw', 'se', 'sw']
170
+ for (const dir of dirs) {
171
+ const handle = document.createElement('i')
172
+ handle.className = `rs rs-${dir}`
173
+ handle.dataset.dir = dir
174
+ el.appendChild(handle)
175
+ handle.addEventListener('pointerdown', (event) => {
176
+ if (el.classList.contains('maximized') || desktop.clientWidth < 600) return
177
+ event.preventDefault()
178
+ event.stopPropagation()
179
+ handle.setPointerCapture(event.pointerId)
180
+ const start = el.getBoundingClientRect()
181
+ const ox = event.clientX
182
+ const oy = event.clientY
183
+ const minW = Number(el.dataset.minW || 280)
184
+ const minH = Number(el.dataset.minH || 160)
185
+ const move = (e) => {
186
+ let x = start.left
187
+ let y = start.top
188
+ let w = start.width
189
+ let h = start.height
190
+ if (dir.includes('e')) w = clampSize(e.clientX - ox + start.width, minW, desktop.clientWidth - x - 4)
191
+ if (dir.includes('s')) h = clampSize(e.clientY - oy + start.height, minH, desktop.clientHeight - y - 4)
192
+ if (dir.includes('w')) {
193
+ const nextW = clampSize(start.width - (e.clientX - ox), minW, start.right - 4)
194
+ x = start.right - nextW
195
+ w = nextW
196
+ }
197
+ if (dir.includes('n')) {
198
+ const nextH = clampSize(start.height - (e.clientY - oy), minH, start.bottom - 4)
199
+ y = start.bottom - nextH
200
+ h = nextH
201
+ }
202
+ el.style.left = Math.max(0, x) + 'px'
203
+ el.style.top = Math.max(0, y) + 'px'
204
+ el.style.width = w + 'px'
205
+ el.style.height = h + 'px'
206
+ }
207
+ const stop = (e) => {
208
+ handle.removeEventListener('pointermove', move)
209
+ handle.removeEventListener('pointerup', stop)
210
+ handle.removeEventListener('pointercancel', stop)
211
+ try { handle.releasePointerCapture(e.pointerId) } catch {}
212
+ }
213
+ handle.addEventListener('pointermove', move)
214
+ handle.addEventListener('pointerup', stop)
215
+ handle.addEventListener('pointercancel', stop)
216
+ })
217
+ }
218
+ }
219
+ const clampSize = (v, min, max) => Math.max(min, Math.min(max, v))
220
+
61
221
  function focus(el) {
62
222
  state.focused = el
63
223
  el.style.zIndex = ++state.z
@@ -97,6 +257,7 @@
97
257
  let dx = 0, dy = 0, dragging = false
98
258
  handle.addEventListener('pointerdown', (event) => {
99
259
  if (event.target.closest('button')) return
260
+ if (el.classList.contains('maximized')) return
100
261
  dragging = true
101
262
  const rect = el.getBoundingClientRect()
102
263
  dx = event.clientX - rect.left
@@ -129,7 +290,7 @@
129
290
  for (const row of rows) {
130
291
  const el = document.createElement('div')
131
292
  el.className = 'row'
132
- el.innerHTML = `<span class="dot"></span><div><b>${row.name}</b><br><small>${row.note}</small></div>`
293
+ el.innerHTML = `<span class="dot"></span><div><b>${esc(row.name)}</b><br><small>${esc(row.note)}</small></div>`
133
294
  list.appendChild(el)
134
295
  }
135
296
  }
@@ -175,23 +336,100 @@
175
336
  const summary = Object.entries(perms).map(([key, value]) => `${key}=${value}`).join(' · ') || '无额外权限'
176
337
  const row = document.createElement('div')
177
338
  row.className = 'row'
178
- row.innerHTML = `<span class="dot"></span><div><b>${mod.name || mod.id}</b> <small>v${mod.version || '0.0.0'}</small><br><small>${summary}</small></div>`
339
+ row.innerHTML = `<span class="dot"></span><div><b>${esc(mod.name || mod.id)}</b> <small>v${esc(mod.version || '0.0.0')}</small><br><small>${esc(summary)}</small></div>`
179
340
  list.appendChild(row)
180
341
  }
181
342
  }
182
343
 
183
344
  function mountSettings(body) {
184
345
  const theme = localStorage.getItem('spak-theme') || 'dark'
346
+ const roots = (() => {
347
+ try { return JSON.parse(localStorage.getItem('spak2os.roots.v1') || '[]') || [] } catch { return [] }
348
+ })()
349
+ const autoScan = localStorage.getItem('spak2os.autoScan.v1') !== 'false'
350
+ const contributed = state.installed.filter(app => app.web).length
351
+
185
352
  body.innerHTML = `
186
- <p>外观</p>
187
- <div class="row"><span class="dot"></span><div>主题:<b data-theme-label>${theme}</b></div>
188
- <button class="tile" data-act="theme" style="margin-left:auto;padding:8px 14px">切换</button></div>
189
- <p style="margin-top:16px">输入</p>
190
- <p>键盘:Alt+Tab 切换 · Esc 关闭 · Ctrl+K 启动器<br>鼠标:拖动标题栏 · 点击任务栏<br>触屏:单指拖动 · 双指滚动</p>`
191
- $('[data-act="theme"]', body).onclick = () => {
192
- toggleTheme()
193
- $('[data-theme-label]', body).textContent = localStorage.getItem('spak-theme')
353
+ <div class="settings-shell">
354
+ <section class="settings-card">
355
+ <div class="settings-head"><div><b>扩展 APPS</b><small>由 spak2os 只读聚合 · 已发现 ${contributed} 个</small></div>
356
+ <button class="settings-btn" data-act="rescan">重新扫描</button></div>
357
+ <div class="settings-line"><span class="dot ${state.bridge ? '' : 'off'}"></span>
358
+ <span>${state.bridge ? 'spak2os 已连接 · 只读' : '未安装 spak2os · 只读桥不可用'}</span></div>
359
+ </section>
360
+
361
+ <section class="settings-card">
362
+ <div class="settings-head"><div><b>媒体目录池</b><small>Music / NV 共用 · 只读扫描</small></div>
363
+ <button class="settings-btn" data-act="scan" ${state.bridge ? '' : 'disabled'}>扫描</button></div>
364
+ <div class="settings-roots" data-roots></div>
365
+ <div class="settings-add">
366
+ <input data-root-input placeholder="/storage/emulated/0/Music" aria-label="添加扫描目录">
367
+ <button class="settings-btn" data-act="add-root">添加</button>
368
+ </div>
369
+ <label class="settings-line"><input type="checkbox" data-act="auto" ${autoScan ? 'checked' : ''}> 打开 Music / NV 时自动扫描</label>
370
+ </section>
371
+
372
+ <section class="settings-card">
373
+ <div class="settings-head"><div><b>外观与输入</b><small>主题 ${esc(theme)}</small></div>
374
+ <button class="settings-btn" data-act="theme">切换主题</button></div>
375
+ <p class="settings-help">键盘:Alt+Tab 切换 · Esc 关闭 · Ctrl+K 启动器<br>触屏:单指拖动 · 双击标题栏最大化</p>
376
+ </section>
377
+ </div>`
378
+
379
+ const renderRoots = () => {
380
+ const list = $('[data-roots]', body)
381
+ const current = (() => {
382
+ try { return JSON.parse(localStorage.getItem('spak2os.roots.v1') || '[]') || [] } catch { return [] }
383
+ })()
384
+ list.innerHTML = current.length
385
+ ? current.map(root => `<div class="settings-root"><span>${esc(root)}</span><button data-remove-root="${esc(root)}" aria-label="移除">×</button></div>`).join('')
386
+ : '<div class="settings-empty">还没有扫描目录。添加后,Music / NV 会读取其中的媒体元数据。</div>'
387
+ }
388
+
389
+ const saveRoots = (next) => {
390
+ localStorage.setItem('spak2os.roots.v1', JSON.stringify(next))
391
+ renderRoots()
194
392
  }
393
+
394
+ body.addEventListener('click', async (event) => {
395
+ const remove = event.target.closest('[data-remove-root]')
396
+ if (remove) {
397
+ let list = []
398
+ try { list = JSON.parse(localStorage.getItem('spak2os.roots.v1') || '[]') || [] } catch {}
399
+ saveRoots(list.filter(root => root !== remove.dataset.removeRoot))
400
+ return
401
+ }
402
+ const action = event.target.closest('[data-act]')?.dataset.act
403
+ if (action === 'theme') {
404
+ toggleTheme()
405
+ toast('主题已切换')
406
+ } else if (action === 'add-root') {
407
+ const input = $('[data-root-input]', body)
408
+ const value = input.value.trim()
409
+ if (!value) return
410
+ let list = []
411
+ try { list = JSON.parse(localStorage.getItem('spak2os.roots.v1') || '[]') || [] } catch {}
412
+ if (!list.includes(value)) list.push(value)
413
+ saveRoots(list)
414
+ input.value = ''
415
+ toast('已加入媒体目录池')
416
+ } else if (action === 'rescan') {
417
+ try {
418
+ const count = await refreshApps()
419
+ toast(`已重新扫描 · ${count} 个扩展 APP`)
420
+ } catch (error) {
421
+ toast(`扫描失败:${error.message}`)
422
+ }
423
+ } else if (action === 'scan') {
424
+ if (!state.bridge) return toast('先安装 spak2os')
425
+ toast('目录池已更新 · 打开 Music 或 NV 即可扫描')
426
+ }
427
+ })
428
+
429
+ $('[data-act="auto"]', body).addEventListener('change', (event) => {
430
+ localStorage.setItem('spak2os.autoScan.v1', event.target.checked ? 'true' : 'false')
431
+ })
432
+ renderRoots()
195
433
  }
196
434
 
197
435
  /* 叶序螺旋点阵(黄金角 137.507764°):r = c*sqrt(n), theta = n*phi */
@@ -223,12 +461,43 @@
223
461
  setTimeout(() => el.remove(), 1800)
224
462
  }
225
463
 
464
+ /** 把 spak2os 聚合来的 APPS 注册进桌面;内建窗口永远优先。 */
465
+ function syncContributedApps() {
466
+ for (const app of state.installed) {
467
+ if (!app.web || !/^[a-z0-9][a-z0-9._-]*$/.test(app.id || '') || BUILTIN[app.id]) continue
468
+ const style = APP_STYLE[app.id] || {}
469
+ APPS[app.id] = {
470
+ title: app.name || app.id,
471
+ glyph: style.glyph || svg('<rect x="3" y="4" width="18" height="16" rx="3"/><path d="M3 9h18M8 4v5"/>'),
472
+ accent: app.accent || style.accent || '#ffcf87',
473
+ web: `/apps/${app.id}/`,
474
+ size: {
475
+ w: Number(app.width) > 0 ? Number(app.width) : 980,
476
+ h: Number(app.height) > 0 ? Number(app.height) : 660,
477
+ },
478
+ }
479
+ }
480
+ renderIcons()
481
+ renderLauncher()
482
+ }
483
+
484
+ async function refreshApps() {
485
+ const response = await fetch('/api/apps?refresh=1')
486
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
487
+ const body = await response.json()
488
+ state.installed = body.apps || []
489
+ state.bridge = Boolean(body.bridge)
490
+ syncContributedApps()
491
+ return state.installed.filter(app => app.web).length
492
+ }
493
+
226
494
  function renderIcons() {
227
495
  iconBox.innerHTML = ''
228
496
  for (const [id, app] of Object.entries(APPS)) {
229
497
  const btn = document.createElement('button')
230
498
  btn.className = 'icon'
231
- btn.innerHTML = `<span class="glyph">${app.glyph}</span><span>${app.title}</span>`
499
+ btn.style.setProperty('--app-accent', app.accent || '#ffcf87')
500
+ btn.innerHTML = `<span class="glyph">${app.glyph}</span><span>${esc(app.title)}</span>`
232
501
  btn.onclick = () => openApp(id)
233
502
  iconBox.appendChild(btn)
234
503
  }
@@ -240,8 +509,9 @@
240
509
  for (const [id, app] of Object.entries(APPS)) {
241
510
  const tile = document.createElement('button')
242
511
  tile.className = 'tile'
512
+ tile.style.setProperty('--app-accent', app.accent || '#ffcf87')
243
513
  tile.dataset.name = `${app.title} ${id}`.toLowerCase()
244
- tile.innerHTML = `<span class="glyph">${app.glyph}</span><span>${app.title}</span>`
514
+ tile.innerHTML = `<span class="glyph">${app.glyph}</span><span>${esc(app.title)}</span>`
245
515
  tile.onclick = () => { launcher.hidden = true; openApp(id) }
246
516
  grid.appendChild(tile)
247
517
  }
@@ -296,9 +566,14 @@
296
566
  setInterval(tick, 1000)
297
567
  try {
298
568
  const response = await fetch('/api/apps')
299
- if (response.ok) state.installed = (await response.json()).apps || []
569
+ if (response.ok) {
570
+ const body = await response.json()
571
+ state.installed = body.apps || []
572
+ state.bridge = Boolean(body.bridge)
573
+ }
300
574
  const mods = await fetch('/api/mods')
301
575
  if (mods.ok) state.mods = (await mods.json()).mods || []
576
+ syncContributedApps()
302
577
  } catch { /* 离线也照常启动 */ }
303
578
  setTimeout(() => {
304
579
  $('#boot').classList.add('done')