diff --git a/apps/server/src/modules/browser/__tests__/browser-e2e.test.ts b/apps/server/src/modules/browser/__tests__/browser-e2e.test.ts index 0cd4bce2..cbd8f66d 100644 --- a/apps/server/src/modules/browser/__tests__/browser-e2e.test.ts +++ b/apps/server/src/modules/browser/__tests__/browser-e2e.test.ts @@ -96,7 +96,10 @@ describe.skipIf(!up)("e2e:登录失效 → 人工接管 → 交还 → Agent 重 const fresh = await invoke("browser.snapshot", { siteId: h.siteId }); expect(fresh).toMatchObject({ controller: "automation", loginRequired: false, waitingReason: null }); expect(fresh.snapshot.text).toContain("种子列表"); - expect((await h.req(`/browser/sessions/${sid}`)).body.data.needsSnapshot).toBe(false); + const afterAgent = (await h.req(`/browser/sessions/${sid}`)).body.data; + expect(afterAgent.needsSnapshot).toBe(false); + // 原站视图据此显示「Agent 正在操作」;人工动作不刷新它 + expect(Date.now() - Date.parse(afterAgent.lastAutomationAt)).toBeLessThan(60_000); const dl = await invoke("browser.download", { sessionId: sid, target: { ref: "e11" }, timeoutMs: 2_000 }); expect(dl).toMatchObject({ sourceReleaseId: h.releaseId, diff --git a/apps/server/src/modules/browser/service.ts b/apps/server/src/modules/browser/service.ts index d3cd202a..4d411839 100644 --- a/apps/server/src/modules/browser/service.ts +++ b/apps/server/src/modules/browser/service.ts @@ -161,6 +161,8 @@ export interface RecoveryState { /** 打开 / 重连时浏览器服务的实例标识;变了 = 浏览器服务重启过 */ instanceId?: string | null; reconnect?: BrowserReconnectState; + /** 最近一次自动化(Agent 能力)动作成功的时间;原站视图据此区分「Agent 正在操作」与「无人操作」 */ + lastAutomationAt?: string; } /** 同一浏览器服务的实例标识多久查一次 */ @@ -674,6 +676,7 @@ export class BrowserService { credential: rs.credential ?? null, needsReconnect: rs.needsReconnect === true, reconnect: rs.reconnect ?? null, + lastAutomationAt: rs.lastAutomationAt ?? null, }; } @@ -1263,7 +1266,10 @@ export class BrowserService { throw fromBrowserError(e2); } } - if (opts.persist !== false) await this.persist(id, loaded.provider, opts.patch?.(out) ?? {}); + const automation: RecoveryState = actor.kind === "automation" ? { lastAutomationAt: this.now().toISOString() } : {}; + if (opts.persist !== false) + await this.persist(id, loaded.provider, { ...(opts.patch?.(out) ?? {}), ...automation }); + else if (automation.lastAutomationAt) await this.patchRecovery(await this.sessionRow(id), automation); this.maybeSyncCookies(row); return out; } diff --git a/apps/server/src/modules/sources-identity/routes/workbench.ts b/apps/server/src/modules/sources-identity/routes/workbench.ts index d9156514..623a84d3 100644 --- a/apps/server/src/modules/sources-identity/routes/workbench.ts +++ b/apps/server/src/modules/sources-identity/routes/workbench.ts @@ -5,7 +5,7 @@ import { errorBody } from "../../../http/errors.ts"; import type { ModuleApp } from "../../types.ts"; import { route } from "../http.ts"; import type { SourcesRuntime } from "../runtime.ts"; -import { detailScreenshotUrl } from "../service/detail-page.ts"; +import { detailScreenshotRequest } from "../service/detail-page.ts"; import { fetchPublicImage, imageResponse } from "../service/image-proxy.ts"; import { listSiteReleases, releaseDetail } from "../service/workbench.ts"; @@ -53,9 +53,13 @@ export function workbenchRoutes(app: ModuleApp, rt: () => SourcesRuntime) { return c.json(errorBody("bad_request", "截图地址无效"), 400); } const r = rt(); - const url = await detailScreenshotUrl(r, rid, index); - if (!url) return c.json(errorBody("not_found", "截图不存在或详情缓存已过期"), 404); - const got = await fetchPublicImage(r.overrides.fetch ?? fetch, url, { maxBytes: SHOT_MAX_BYTES, label: "截图" }); + const req = await detailScreenshotRequest(r, rid, index); + if (!req) return c.json(errorBody("not_found", "截图不存在或详情缓存已过期"), 404); + const got = await fetchPublicImage(r.overrides.fetch ?? fetch, req.url, { + maxBytes: SHOT_MAX_BYTES, + label: "截图", + ...(req.referer ? { referer: req.referer } : {}), + }); if (!got.ok) return c.json(errorBody("upstream_unavailable", got.message), 502); return imageResponse(got, 86_400); }); diff --git a/apps/server/src/modules/sources-identity/service/detail-page.ts b/apps/server/src/modules/sources-identity/service/detail-page.ts index ec95f634..048e51f3 100644 --- a/apps/server/src/modules/sources-identity/service/detail-page.ts +++ b/apps/server/src/modules/sources-identity/service/detail-page.ts @@ -64,6 +64,38 @@ export async function detailScreenshotUrl(rt: SourcesRuntime, srId: string, inde return (await loadStored(rt, srId))?.screenshots[index] ?? null; } +/** 根域(去 www.、取最后两级):pic.hhanclub.net 与 hhanclub.net 视为同站 */ +function rootHost(u: string): string | null { + try { + const host = new URL(u).hostname.toLowerCase().replace(/^www\./, ""); + return /^[\d.]+$/.test(host) || host.includes(":") ? host : host.split(".").slice(-2).join("."); + } catch { + return null; + } +} + +/** + * 截图代理请求:原始地址 + 站点自家图床(同根域,如 pic.hhanclub.net / proxy-cover.hhanclub.net)要带的 Referer。 + * 站点 CDN 常做防盗链,只认本站 Referer;外部图床不带 Referer。任何情况下都不带 Cookie。 + */ +export async function detailScreenshotRequest( + rt: SourcesRuntime, + srId: string, + index: number, +): Promise<{ url: string; referer?: string } | null> { + const url = await detailScreenshotUrl(rt, srId, index); + if (!url) return null; + const site = await rt.ctx.db + .selectFrom("source_release as r") + .innerJoin("site as s", "s.id", "r.site_id") + .select(["s.base_url"]) + .where("r.id", "=", srId) + .executeTakeFirst(); + const siteRoot = site ? rootHost(site.base_url) : null; + if (siteRoot && rootHost(url) === siteRoot) return { url, referer: `${new URL(site!.base_url).origin}/` }; + return { url }; +} + export async function loadDetailPage(rt: SourcesRuntime, srId: string): Promise { const p = await loadStored(rt, srId); if (!p) return null; diff --git a/apps/server/src/modules/sources-identity/service/feeds.ts b/apps/server/src/modules/sources-identity/service/feeds.ts index 2882a2f0..35e09b05 100644 --- a/apps/server/src/modules/sources-identity/service/feeds.ts +++ b/apps/server/src/modules/sources-identity/service/feeds.ts @@ -7,6 +7,7 @@ import { type FeedFetchState, type FeedItem, getSiteDefinition, + normalizeSiteFeedItem, } from "@umt/adapters"; import type { FeedHealth } from "@umt/contracts"; import { type FeedSourceRow, type SiteRow, sql } from "@umt/db"; @@ -157,6 +158,9 @@ export async function ingestFeed( const url = await secrets.resolve(feed.url_secret_ref); const result = await rt.rss.fetchFeed(url, stateFromRow(feed), { ignoreBackoff: !!opts.manual }); + // 挂站点的 Feed:每次真正发出的 RSS 请求也记进站点请求计数(熔断 / 退避跳过的不算) + if (site && result.kind !== "skipped") rt.countSiteRequest(site.id); + if (site) await renormalizeFeedReleases(rt, site).catch(() => {}); const now = rt.now(); const auth = isAuthFailure(result); const health: FeedHealth = auth ? "failing" : healthOf(result); @@ -175,7 +179,7 @@ export async function ingestFeed( if (result.kind === "ok") { const applied = applyFeedItems(window, result.feed.items); window = applied.cursor; - const counts = await writeItems(rt, trx, feed, site?.id ?? null, applied.newItems, applied.updatedItems); + const counts = await writeItems(rt, trx, feed, site ?? null, applied.newItems, applied.updatedItems); const unchanged = result.feed.items .filter((i) => !applied.newItems.includes(i) && !applied.updatedItems.includes(i)) .map((i) => i.guid); @@ -312,16 +316,36 @@ function observationRaw(item: FeedItem) { }; } +/** 规范化后的 RSS 条目字段(写进 source_release.raw.rss,v 为规范化版本) */ +export const RSS_NORMALIZE_VERSION = 1; + +function feedFields(siteKey: string, item: Pick) { + const n = normalizeSiteFeedItem(siteKey, item.title); + // 分类:标题前缀 → RSS (可能是名称) + const catName = n.category.name ?? item.categories[0] ?? null; + return { + title: n.title, + subtitle: n.subtitle, + categoryRaw: n.category.id ?? catName, + categoryName: catName, + mediaType: n.mediaType, + // enclosure length 是精确字节数,标题尾部的大小是四舍五入后的显示值 + sizeBytes: item.size > 0 ? item.size : n.sizeBytes, + uploader: n.uploader, + }; +} + async function writeItems( rt: SourcesRuntime, trx: Exec, feed: FeedSourceRow, - siteId: string | null, + site: Pick | null, newItems: FeedItem[], updatedItems: FeedItem[], ): Promise<{ newReleases: number }> { let newReleases = 0; const updated = new Set(updatedItems); + const siteId = site?.id ?? null; for (const item of [...newItems, ...updatedItems]) { const raw = observationRaw(item); const obs = ( @@ -337,7 +361,7 @@ async function writeItems( returning id, source_release_id, (select content_digest from prev) as prev_digest`.execute(trx) ).rows[0]!; - if (!siteId) { + if (!site || !siteId) { // 无站点的通用 Feed:只留观察,无法按站点资源聚合 await trx .updateTable("observation") @@ -349,20 +373,36 @@ async function writeItems( const srid = extractSiteReleaseId(item) ?? `guid:${createHash("sha1").update(item.guid).digest("hex").slice(0, 20)}`; const contentChanged = updated.has(item) || (obs.prev_digest !== null && obs.prev_digest !== item.rawDigest); - const sizeBytes = item.size > 0 ? item.size : null; + const f = feedFields(site.key, item); + const rssRaw = { + link: item.link, + categories: item.categories, + rawTitle: item.title, + categoryName: f.categoryName, + mediaType: f.mediaType, + ...(f.uploader ? { uploader: f.uploader } : {}), + v: RSS_NORMALIZE_VERSION, + }; + // 与列表快照按站点资源 ID 合并:快照见过(raw._site)的行,标题 / 副标题 / 分类 / 做种 / 促销 / H&R 以快照为准, + // RSS 只补空缺;快照没见过的行,内容变化时用 RSS 的新值 const rel = ( await sql<{ id: string; inserted: boolean; prev_title: string | null; resolution_locked: boolean }>` with prev as (select title from source_release where site_id = ${siteId} and site_release_id = ${srid}) - insert into source_release (site_id, site_release_id, title, category_raw, size_bytes, source_published_at, raw) - values (${siteId}, ${srid}, ${item.title}, ${item.categories[0] ?? null}, ${sizeBytes}, ${item.publishedAt ?? null}, - ${jsonb({ rss: { link: item.link, categories: item.categories } })}::jsonb) + insert into source_release (site_id, site_release_id, title, subtitle, category_raw, size_bytes, source_published_at, raw) + values (${siteId}, ${srid}, ${f.title}, ${f.subtitle}, ${f.categoryRaw}, ${f.sizeBytes}, ${item.publishedAt ?? null}, + ${jsonb({ rss: rssRaw })}::jsonb) on conflict (site_id, site_release_id) do update set last_seen_at = now(), - title = case when ${contentChanged} then excluded.title else source_release.title end, + title = case when source_release.raw ? '_site' then source_release.title + when ${contentChanged} then excluded.title else source_release.title end, + subtitle = case when source_release.raw ? '_site' then coalesce(source_release.subtitle, excluded.subtitle) + when ${contentChanged} then coalesce(excluded.subtitle, source_release.subtitle) + else coalesce(source_release.subtitle, excluded.subtitle) end, size_bytes = case when ${contentChanged} then coalesce(excluded.size_bytes, source_release.size_bytes) else coalesce(source_release.size_bytes, excluded.size_bytes) end, source_published_at = coalesce(source_release.source_published_at, excluded.source_published_at), - category_raw = coalesce(source_release.category_raw, excluded.category_raw) + category_raw = coalesce(source_release.category_raw, excluded.category_raw), + raw = source_release.raw || excluded.raw returning id, (xmax = 0) as inserted, (select title from prev) as prev_title, resolution_locked`.execute(trx) ).rows[0]!; await trx @@ -379,7 +419,7 @@ async function writeItems( { sourceReleaseId: rel.id, observationId: obs.id, siteId, isNew: true }, { idempotencyKey: `release.observed:${rel.id}` }, ); - } else if (contentChanged && rel.prev_title !== item.title && !rel.resolution_locked) { + } else if (contentChanged && rel.prev_title !== f.title && !rel.resolution_locked) { // 标题修正:按新标题重新识别(证据版本变化) await rt.ctx.outbox.emit( trx, @@ -391,3 +431,50 @@ async function writeItems( } return { newReleases }; } + +/** + * 旧 RSS 行补规范化:规范化上线前写进来的站点 RSS 行(raw.rss 没有 v,标题还是 `[分类]…[大小]` 整串), + * 在该站 Feed 下次抓取时按原标题重算一次(每次最多 500 行,不请求站点)。快照见过的行不动。 + * 标题变了且未锁定的,重新发 release.observed 让身份解析按干净标题再识别。 + */ +export async function renormalizeFeedReleases(rt: SourcesRuntime, site: Pick): Promise { + const rows = await rt.ctx.db + .selectFrom("source_release") + .select(["id", "title", "subtitle", "category_raw", "size_bytes", "raw", "resolution_locked"]) + .where("site_id", "=", site.id) + .where(sql`raw ? 'rss'`) + .where(sql`not (raw ? '_site')`) + .where(sql`coalesce((raw->'rss'->>'v')::int, 0) < ${RSS_NORMALIZE_VERSION}`) + .limit(500) + .execute(); + if (!rows.length) return 0; + await rt.ctx.db.transaction().execute(async (trx) => { + for (const r of rows) { + const rss = ((r.raw ?? {}) as { rss?: Record }).rss ?? {}; + const rawTitle = typeof rss.rawTitle === "string" ? rss.rawTitle : r.title; + const categories = Array.isArray(rss.categories) ? (rss.categories as string[]) : []; + const f = feedFields(site.key, { title: rawTitle, size: Number(r.size_bytes ?? 0), categories }); + const nextRss = { + ...rss, + rawTitle, + categoryName: f.categoryName, + mediaType: f.mediaType, + ...(f.uploader ? { uploader: f.uploader } : {}), + v: RSS_NORMALIZE_VERSION, + }; + await sql`update source_release set title = ${f.title}, subtitle = coalesce(subtitle, ${f.subtitle}), + category_raw = coalesce(${f.categoryRaw}, category_raw), size_bytes = coalesce(size_bytes, ${f.sizeBytes}), + raw = raw || jsonb_build_object('rss', ${JSON.stringify(nextRss)}::jsonb) + where id = ${r.id}`.execute(trx); + if (f.title !== r.title && !r.resolution_locked) { + await rt.ctx.outbox.emit( + trx, + "release.observed", + { sourceReleaseId: r.id, observationId: null, siteId: site.id, isNew: false }, + { idempotencyKey: `release.observed:${r.id}:rss-norm-${RSS_NORMALIZE_VERSION}` }, + ); + } + } + }); + return rows.length; +} diff --git a/apps/server/test/sources-identity/rss-normalize.test.ts b/apps/server/test/sources-identity/rss-normalize.test.ts new file mode 100644 index 00000000..6579d319 --- /dev/null +++ b/apps/server/test/sources-identity/rss-normalize.test.ts @@ -0,0 +1,181 @@ +import type { SiteRelease } from "@umt/adapters"; +import { createMockRssServer, type MockFeedItem, type MockRssServer } from "@umt/testkit"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { ingestFeed } from "../../src/modules/sources-identity/service/feeds.ts"; +import { upsertSiteRelease } from "../../src/modules/sources-identity/service/snapshot.ts"; +import { listSiteReleases } from "../../src/modules/sources-identity/service/workbench.ts"; +import { createHarness, type Harness, pgAvailable, seedFeed, seedSite } from "./harness.ts"; + +// HH-2:憨憨 RSS 条目规范化(`[分类]种子名[副标题][大小]`)、与列表快照合并、旧行补规范化、RSS 请求计数 +const up = await pgAvailable(); +const BASE = "https://hhanclub.example"; + +const item = (id: string, title: string, pubDate: string, extra: Partial = {}): MockFeedItem => ({ + guid: `hh-${id}`, + title, + link: `${BASE}/details.php?id=${id}`, + enclosureUrl: `${BASE}/download.php?id=${id}&passkey=PK`, + size: 731525231, + pubDate: new Date(pubDate), + categories: [], + ...extra, +}); + +const snapshotRelease = (id: string, title: string, uploadedAt: string): SiteRelease => ({ + siteId: "hhanclub", + siteReleaseId: id, + title, + subtitle: "快照副标题", + category: { id: "402", name: "电视剧" }, + sizeBytes: 1024, + seeders: 22, + leechers: 66, + snatched: 5, + promotion: { type: "free", downloadFactor: 0, uploadFactor: 1, expiresAt: null }, + hr: false, + uploadedAt, + detailUrl: `${BASE}/details.php?id=${id}`, + tags: [], + raw: {}, + observedAt: new Date().toISOString(), +}); + +describe.skipIf(!up)("站点 RSS 条目规范化(HH-2)", () => { + let h: Harness; + let siteId: string; + const servers: MockRssServer[] = []; + + beforeAll(async () => { + h = await createHarness("rssnorm"); + siteId = (await seedSite(h, { key: "hhanclub", baseUrl: `${BASE}/`, cookie: "c=1" })).site.id; + }); + afterAll(async () => { + for (const s of servers) await s.close(); + await h?.close(); + }); + + const feed = async (items: MockFeedItem[]) => { + const s = await createMockRssServer({ items, passkey: "PK" }); + servers.push(s); + return { server: s, row: await seedFeed(h, { siteId, url: s.feedUrl, name: "憨憨 RSS" }) }; + }; + const rel = (id: string) => + h.db + .selectFrom("source_release") + .selectAll() + .where("site_id", "=", siteId) + .where("site_release_id", "=", id) + .executeTakeFirstOrThrow(); + + it("拆出分类 / 副标题 / 干净标题;大小取 enclosure 精确值;原始标题留在 raw.rss;请求计入站点计数", async () => { + const { row } = await feed([ + item( + "219390", + "[音乐]Travis - L.A. Times (2024) - WEB-DL - 24bit ALAC-HHWEB[Travis - L.A. Times | ALAC | 苹果音乐][697.64 MB]", + "2026-09-29T08:51:01Z", + ), + item("219391", "[纪录片]Some Doc 2026 2160p WEB-DL-HHWEB[新][某纪录片][1.5 GB]", "2026-09-29T08:10:00Z", { + size: 0, + }), + ]); + expect((await ingestFeed(h.rt, row.id)).status).toBe("ok"); + const r = await rel("219390"); + expect(r).toMatchObject({ + title: "Travis - L.A. Times (2024) - WEB-DL - 24bit ALAC-HHWEB", + subtitle: "Travis - L.A. Times | ALAC | 苹果音乐", + category_raw: "410", + }); + expect(Number(r.size_bytes)).toBe(731525231); + expect((r.raw as { rss: Record }).rss).toMatchObject({ + categoryName: "音乐", + mediaType: "music", + rawTitle: expect.stringContaining("[697.64 MB]"), + v: 1, + }); + const doc = await rel("219391"); + expect(doc).toMatchObject({ title: "Some Doc 2026 2160p WEB-DL-HHWEB", subtitle: "某纪录片", category_raw: "404" }); + expect(Number(doc.size_bytes)).toBe(Math.round(1.5 * 1024 ** 3)); + + // 计数是异步写的,等一下 + let n = 0; + for (let i = 0; i < 50 && n === 0; i++) { + n = await h.rt.siteRequestCount(siteId); + if (!n) await new Promise((res) => setTimeout(res, 20)); + } + expect(n).toBe(1); + }); + + it("与列表快照按站点资源 ID 合并:快照补做种 / 促销 / H&R,之后 RSS 更新不再覆盖快照标题", async () => { + const { server, row } = await feed([ + item("219380", "[电视剧]Show S01 2026 1080p WEB-DL-HHWEB[示例剧 | 第1集][1.2 GB]", "2026-09-29T07:00:00Z"), + ]); + await ingestFeed(h.rt, row.id); + await h.db + .transaction() + .execute((trx) => + upsertSiteRelease( + h.rt, + trx, + { id: siteId }, + snapshotRelease("219380", "Show S01 2026 1080p WEB-DL-HHWEB", "2026-09-29T07:00:00Z"), + ), + ); + let r = await rel("219380"); + expect(r).toMatchObject({ seeders: 22, leechers: 66, subtitle: "快照副标题", category_raw: "402" }); + expect((r.raw as { _site: { hr: boolean } })._site.hr).toBe(false); + expect((r.raw as { rss: { categoryName: string } }).rss.categoryName).toBe("电视剧"); + + server.updateItem("hh-219380", { title: "[电视剧]Show S01 2026 1080p WEB-DL-HHWEB[示例剧 | 第1-2集][2.4 GB]" }); + await ingestFeed(h.rt, row.id, { manual: true }); + r = await rel("219380"); + expect(r.title).toBe("Show S01 2026 1080p WEB-DL-HHWEB"); + expect(r.seeders).toBe(22); + }); + + it("规范化前写进来的旧 RSS 行:下次抓取时按原标题补规范化并重新触发识别", async () => { + const dirty = "[音乐]Old Album (2020) - WEB-DL - 16bit ALAC-HHWEB[Old Album | ALAC][120.5 MB]"; + const old = await h.db + .insertInto("source_release") + .values({ + site_id: siteId, + site_release_id: "219001", + title: dirty, + size_bytes: 126353408, + raw: JSON.stringify({ rss: { link: `${BASE}/details.php?id=219001`, categories: [] } }), + }) + .returning(["id"]) + .executeTakeFirstOrThrow(); + const { row } = await feed([ + item("219002", "[电影]New Movie 2026 1080p-HHWEB[新片][4 GB]", "2026-09-29T09:00:00Z"), + ]); + await ingestFeed(h.rt, row.id); + const r = await rel("219001"); + expect(r).toMatchObject({ + title: "Old Album (2020) - WEB-DL - 16bit ALAC-HHWEB", + subtitle: "Old Album | ALAC", + category_raw: "410", + }); + expect((r.raw as { rss: { rawTitle: string } }).rss.rawTitle).toBe(dirty); + const events = (await h.outbox("release.observed")).filter((e) => e.payload.sourceReleaseId === old.id); + expect(events.map((e) => e.payload.isNew)).toEqual([false]); + }); + + it("资源列表按发布时间排序:缺做种 / H&R 的 RSS 条目不会因此排到前面", async () => { + await h.db + .transaction() + .execute((trx) => + upsertSiteRelease( + h.rt, + trx, + { id: siteId }, + snapshotRelease("219500", "Newest Snapshot 2026-HHWEB", "2026-09-29T10:00:00Z"), + ), + ); + const list = await listSiteReleases(h.rt, siteId, { limit: 50, live: "false" }); + const ids = list.items.map((i) => i.siteReleaseId); + expect(ids[0]).toBe("219500"); + expect(ids.indexOf("219390")).toBeLessThan(ids.indexOf("219391")); + const times = list.items.map((i) => Date.parse(i.sourcePublishedAt ?? i.firstSeenAt)); + expect([...times].sort((a, b) => b - a)).toEqual(times); + }); +}); diff --git a/apps/web/app/components/agent/agent-interaction.tsx b/apps/web/app/components/agent/agent-interaction.tsx index b181ab28..4d0ded93 100644 --- a/apps/web/app/components/agent/agent-interaction.tsx +++ b/apps/web/app/components/agent/agent-interaction.tsx @@ -28,7 +28,7 @@ export function planTodoItems(steps: AgentPlanStep[]): TodoItem[] { id: s.id, title: s.title, status: STEP_STATUS[s.status], - ...(s.note ? { detail: {s.note} } : {}), + ...(s.note ? { detail: {s.note} } : {}), })); } diff --git a/apps/web/app/components/agentui/todo-list.tsx b/apps/web/app/components/agentui/todo-list.tsx index b0d32448..f81dafc1 100644 --- a/apps/web/app/components/agentui/todo-list.tsx +++ b/apps/web/app/components/agentui/todo-list.tsx @@ -3,7 +3,7 @@ // 改造:SVG 状态图标保留几何(虚线圈=待办、弧线=进行中、勾=完成、叉=取消),pathLength 弹簧动画换成 // CSS 过渡/animate-spin(motion-reduce 关闭);完成色 emerald → success,取消 rose → destructive,且都配 sr-only 文字; // 表头完成数去掉滚字;折叠用 Disclosure;maxHeight 改成 Tailwind 类;新增 `error` 状态(工具失败)、 -// `skipped` 状态(Agent 步骤清单里跳过的步骤)与 `detail` 槽。 +// `skipped` 状态(Agent 步骤清单里跳过的步骤)与 `detail` 槽(可压缩,标题至少留一半宽度)。 import { ChevronDown, ListTodo } from "lucide-react"; import { type ReactNode, useEffect, useId, useRef, useState } from "react"; import { cn } from "~/lib/utils.ts"; @@ -162,8 +162,10 @@ export function TodoList({ {LABEL[s]}: {item.detail ? ( - {item.detail} + {item.detail} ) : null} ); diff --git a/apps/web/app/components/browser/coords.test.ts b/apps/web/app/components/browser/coords.test.ts index ef5ab875..539f9a30 100644 --- a/apps/web/app/components/browser/coords.test.ts +++ b/apps/web/app/components/browser/coords.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { holderName, toViewport, waitingText } from "./coords.ts"; +import { agentActive, controllerLabel, holderName, toViewport, waitingText } from "./coords.ts"; describe("原站视图坐标换算", () => { const natural = { width: 1280, height: 800 }; @@ -29,3 +29,18 @@ describe("原站视图坐标换算", () => { expect(holderName("admin_session:alice")).toBe("alice"); }); }); + +describe("当前控制文案", () => { + const now = Date.parse("2026-09-29T10:00:00Z"); + it("自动化且没有最近动作 → 「自动化」;两分钟内有自动化动作 → 「Agent」", () => { + expect(controllerLabel({ controller: "automation", lastAutomationAt: null }, now)).toBe("自动化"); + expect(controllerLabel({ controller: "automation" }, now)).toBe("自动化"); + expect(controllerLabel({ controller: "automation", lastAutomationAt: "2026-09-29T09:50:00Z" }, now)).toBe("自动化"); + expect(controllerLabel({ controller: "automation", lastAutomationAt: "2026-09-29T09:59:10Z" }, now)).toBe("Agent"); + expect(agentActive({ controller: "automation", lastAutomationAt: "2026-09-29T09:59:10Z" }, now)).toBe(true); + }); + it("人工接管 / 等待处理不看自动化时间", () => { + expect(controllerLabel({ controller: "human", lastAutomationAt: "2026-09-29T09:59:50Z" }, now)).toBe("人工接管"); + expect(controllerLabel({ controller: "waiting" }, now)).toBe("等待处理"); + }); +}); diff --git a/apps/web/app/components/browser/coords.ts b/apps/web/app/components/browser/coords.ts index 582dae94..7b01e0a9 100644 --- a/apps/web/app/components/browser/coords.ts +++ b/apps/web/app/components/browser/coords.ts @@ -17,12 +17,33 @@ export function toViewport( } export const CONTROLLER_LABEL: Record = { - automation: "Agent", + automation: "自动化", human: "人工接管", waiting: "等待处理", closed: "已关闭", }; +/** 自动化动作在这段时间内发生过,算「Agent 正在操作」 */ +export const AGENT_ACTIVE_MS = 2 * 60_000; + +/** Agent 是否正在用这个会话:控制者是自动化,且最近 AGENT_ACTIVE_MS 内有过自动化动作 */ +export function agentActive( + s: { controller: BrowserController; lastAutomationAt?: string | null }, + now = Date.now(), +): boolean { + if (s.controller !== "automation" || !s.lastAutomationAt) return false; + const t = new Date(s.lastAutomationAt).getTime(); + return Number.isFinite(t) && now - t < AGENT_ACTIVE_MS; +} + +/** 「当前控制」文案:无人操作时是「自动化」,Agent 正在操作才显示「Agent」 */ +export function controllerLabel( + s: { controller: BrowserController; lastAutomationAt?: string | null }, + now = Date.now(), +): string { + return agentActive(s, now) ? "Agent" : CONTROLLER_LABEL[s.controller]; +} + const WAITING_TEXT: Record = { captcha: "登录页需要验证码", challenge: "页面需要人工验证", diff --git a/apps/web/app/components/browser/desktop-view.tsx b/apps/web/app/components/browser/desktop-view.tsx index a29a6a26..9710e7e3 100644 --- a/apps/web/app/components/browser/desktop-view.tsx +++ b/apps/web/app/components/browser/desktop-view.tsx @@ -9,7 +9,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge. import { Button } from "~/components/ui/button.tsx"; import { apiData, errorMessage } from "~/lib/api.ts"; import { qk } from "~/lib/query-keys.ts"; -import { CONTROLLER_LABEL, credentialBadge, holderName, waitingText } from "./coords.ts"; +import { agentActive, controllerLabel, credentialBadge, holderName, waitingText } from "./coords.ts"; import { ReconnectPanel, ReloadNotice, useAutoReconnect } from "./reconnect.tsx"; import { SiteOriginView } from "./site-origin-view.tsx"; @@ -153,7 +153,7 @@ function DesktopView({
{conn.text} - 当前控制:{CONTROLLER_LABEL[s.controller]} + 当前控制:{controllerLabel(s)} {human && leaseHolder ? `(${leaseHolder})` : ""} {waiting ? {waiting} : null} @@ -217,7 +217,9 @@ function DesktopView({

{mine ? "直接在画面里点击和输入。中文、日文用本机输入法输入;复制粘贴用 Ctrl+C / Ctrl+V。" - : "只读观看,Agent 可能正在操作。需要自己点或输入时先接管。"} + : agentActive(s) + ? "只读观看,Agent 正在操作。需要自己点或输入时先接管,Agent 会暂停。" + : "只读观看,目前没有 Agent 在操作。需要自己点或输入时先接管。"}

diff --git a/apps/web/app/components/browser/site-origin-view.tsx b/apps/web/app/components/browser/site-origin-view.tsx index 517b3063..08d8dd3b 100644 --- a/apps/web/app/components/browser/site-origin-view.tsx +++ b/apps/web/app/components/browser/site-origin-view.tsx @@ -34,7 +34,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge. import { Button } from "~/components/ui/button.tsx"; import { Input } from "~/components/ui/input.tsx"; import { errorMessage } from "~/lib/api.ts"; -import { CONTROLLER_LABEL, credentialBadge, holderName, toViewport, waitingText } from "./coords.ts"; +import { controllerLabel, credentialBadge, holderName, toViewport, waitingText } from "./coords.ts"; import { isBrowserGone, ReconnectPanel, ReloadNotice, useAutoReconnect } from "./reconnect.tsx"; // 原站视图(REQ §4.7.1、§4.7.3):同一个 camofox 会话的定时截图 + 人工输入映射。 @@ -156,7 +156,7 @@ export function SiteOriginView({ site }: { site: SiteListItem }) {
{conn.text} - 当前控制:{CONTROLLER_LABEL[s.controller]} + 当前控制:{controllerLabel(s)} {human && leaseHolder ? `(${leaseHolder})` : ""} {waiting ? {waiting} : null} diff --git a/apps/web/app/components/search/release-results.tsx b/apps/web/app/components/search/release-results.tsx index 989b761d..6e77d665 100644 --- a/apps/web/app/components/search/release-results.tsx +++ b/apps/web/app/components/search/release-results.tsx @@ -390,9 +390,9 @@ function ReleaseRow({ r, onDownload }: { r: SearchRelease; onDownload: () => voi {r.sizeBytes === null ? "大小未知" : formatBytes(r.sizeBytes, 1)} - {r.seeders ?? "?"} + {r.seeders ?? "—"} - {r.leechers ?? "?"} + {r.leechers ?? "—"} 发布 {relTime(r.uploadedAt)} {tech ? {tech} : null} diff --git a/apps/web/app/components/workbench/release-detail-sheet.tsx b/apps/web/app/components/workbench/release-detail-sheet.tsx index 4e384974..4bbfeb14 100644 --- a/apps/web/app/components/workbench/release-detail-sheet.tsx +++ b/apps/web/app/components/workbench/release-detail-sheet.tsx @@ -217,7 +217,7 @@ export function ReleaseDetailSheet({ { label: "促销截止", value: formatDateTime(detail.data.volatile.promotionExpiresAt) }, { label: "做种 / 下载", - value: `${r.seeders ?? "?"} / ${r.leechers ?? "?"}(${relTime(detail.data.volatile.peersObservedAt)}观察)`, + value: `${r.seeders ?? "—"} / ${r.leechers ?? "—"}(${relTime(detail.data.volatile.peersObservedAt)}观察)`, }, { label: "详情抓取于", value: formatDateTime(detail.data.volatile.detailFetchedAt) }, ]} diff --git a/apps/web/app/components/workbench/release-fields.ts b/apps/web/app/components/workbench/release-fields.ts index 6cbfd7c7..e520fb3f 100644 --- a/apps/web/app/components/workbench/release-fields.ts +++ b/apps/web/app/components/workbench/release-fields.ts @@ -16,9 +16,23 @@ export function hrOf(r: Pick): "yes" | "no" | "unknown" { return hr === true ? "yes" : hr === false ? "no" : "unknown"; } +/** 站点 RSS 规范化后的字段(raw.rss):分类名、媒体大类 */ +function rssExtra(r: Pick): { categoryName?: unknown } { + const s = (r.raw as { rss?: { categoryName?: unknown } }).rss; + return s && typeof s === "object" ? s : {}; +} + +/** 分类名:列表快照的站点分类名 → RSS 标题前缀里的分类名 → 站点分类 ID */ export function categoryLabel(r: Pick): string | null { const name = siteExtra(r).categoryName; - return typeof name === "string" && name ? name : r.categoryRaw; + if (typeof name === "string" && name) return name; + const rss = rssExtra(r).categoryName; + return typeof rss === "string" && rss ? rss : r.categoryRaw; +} + +/** 列表里的数字:未知显示「—」(未知 ≠ 0) */ +export function countText(n: number | null | undefined): string { + return typeof n === "number" && Number.isFinite(n) ? String(n) : "—"; } const PROMO_LABEL: Record = { diff --git a/apps/web/app/components/workbench/release-list.tsx b/apps/web/app/components/workbench/release-list.tsx index 809a0334..29594f5f 100644 --- a/apps/web/app/components/workbench/release-list.tsx +++ b/apps/web/app/components/workbench/release-list.tsx @@ -6,6 +6,7 @@ import { cn, formatBytes } from "~/lib/utils.ts"; import { absTime, categoryLabel, + countText, hrOf, ORIGIN_LABEL, promotionLabel, @@ -15,8 +16,11 @@ import { snatchedOf, } from "./release-fields.ts"; -/** 促销 / H&R 徽章(列表与详情共用):促销带剩余时间,过期的写明 */ -export function ReleaseBadges({ r }: { r: WorkbenchRelease }) { +/** + * 促销 / H&R 徽章(列表与详情共用):促销带剩余时间,过期的写明。 + * H&R 未知只在 showUnknownHr 时标出(详情里);列表行不为未知字段挂徽章,避免 RSS 条目满屏「未知」。 + */ +export function ReleaseBadges({ r, showUnknownHr = false }: { r: WorkbenchRelease; showUnknownHr?: boolean }) { const promo = promotionLabel(r); const hr = hrOf(r); const left = promo && !promo.expired ? remainingLabel(r.promotionExpiresAt) : null; @@ -31,7 +35,7 @@ export function ReleaseBadges({ r }: { r: WorkbenchRelease }) { ) : null} {hr === "yes" ? H&R : null} - {hr === "unknown" ? H&R 未知 : null} + {hr === "unknown" && showUnknownHr ? H&R 未知 : null} ); } @@ -159,19 +163,17 @@ export function ReleaseRow({ {r.subtitle ?? ""} - - {cat ?? "分类未知"} - - - {r.sizeBytes === null ? "大小未知" : formatBytes(r.sizeBytes, 1)} + + {cat ?? "—"} + {r.sizeBytes === null ? "—" : formatBytes(r.sizeBytes, 1)} - {r.seeders ?? "?"} + {countText(r.seeders)} - {r.leechers ?? "?"} + {countText(r.leechers)} - {snatched ?? "?"} + {countText(snatched)} {relTime(r.sourcePublishedAt)} diff --git a/deno.lock b/deno.lock index 12126f31..05cb264e 100644 --- a/deno.lock +++ b/deno.lock @@ -16,7 +16,6 @@ "npm:@react-router/dev@*": "7.12.0_react-router@7.12.0_typescript@7.0.2_vite@7.3.6_@types+node@24.19.0_react@19.3.0_react-dom@19.3.0_yaml@2.9.1", "npm:@react-router/dev@7.12.0": "7.12.0_react-router@7.12.0_typescript@7.0.2_vite@7.3.6_@types+node@24.19.0_react@19.3.0_react-dom@19.3.0_yaml@2.9.1", "npm:@react-router/node@7.12.0": "7.12.0_react-router@7.12.0_typescript@7.0.2_react@19.3.0_react-dom@19.3.0", - "npm:@simplewebauthn/browser@^13.3.0": "13.3.0", "npm:@simplewebauthn/server@13.3.3": "13.3.3", "npm:@tailwindcss/vite@^4.1.13": "4.3.3_vite@7.3.6_@types+node@24.19.0_yaml@2.9.1", "npm:@tanstack/react-query@^5.90.21": "5.104.0_react@19.3.0", @@ -45,13 +44,12 @@ "npm:pino@10.3.1": "10.3.1", "npm:playwright-core@1.63.0": "1.63.0", "npm:postgres@3.4.9": "3.4.9", - "npm:qr-code-styling@^1.9.2": "1.9.2", "npm:radix-ui@^1.4.3": "1.6.7_@types+react@19.3.0_@types+react-dom@19.3.0_react@19.3.0_react-dom@19.3.0", "npm:react-day-picker@^10.0.1": "10.0.1_@types+react@19.3.0_react@19.3.0", "npm:react-dom@^19.2.4": "19.3.0_react@19.3.0", "npm:react-router@7.12.0": "7.12.0_react@19.3.0_react-dom@19.3.0", "npm:react@^19.2.4": "19.3.0", - "npm:recharts@^3.10.1": "3.10.1_react@19.3.0_react-dom@19.3.0_react-is@19.3.0_@types+react@19.3.0_redux@5.0.1", + "npm:shadcn@^3.8.5": "3.8.5_@types+node@24.19.0_typescript@7.0.2", "npm:sonner@^2.0.7": "2.0.8_@types+react@19.3.0_react@19.3.0_react-dom@19.3.0", "npm:tailwind-merge@^3.5.0": "3.7.0", "npm:tailwindcss@^4.1.13": "4.3.3", @@ -80,15 +78,25 @@ } }, "npm": { + "@antfu/ni@25.0.0": { + "integrity": "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==", + "dependencies": [ + "ansis", + "fzf", + "package-manager-detector", + "tinyexec" + ], + "bin": true + }, "@anthropic-ai/sdk@0.124.0_zod@4.6.5": { "integrity": "sha512-cN5O8i9UVxHeOQAzj/XjshWXG8KiibJDw9OGpH2Z/eR3n/RBxdoLxDJOcfqAJWvjaMDFfHTBADU04hWRJVkDyA==", "dependencies": [ "json-schema-to-ts", "standardwebhooks", - "zod" + "zod@4.6.5" ], "optionalPeers": [ - "zod" + "zod@4.6.5" ], "bin": true }, @@ -96,7 +104,7 @@ "integrity": "sha512-pLMeRgRYS7/vZIgAAkOe2P6+XGTifR1MT9pqDoxZ11EmcJJ+DPmdPqLN8ZZF4U8R9KHCCQCS9c2wtuE8wSrfkw==", "dependencies": [ "openapi3-ts", - "zod" + "zod@4.6.5" ] }, "@aws-sdk/client-bedrock-runtime@3.1127.0": { @@ -595,6 +603,31 @@ "@date-fns/tz@1.5.0": { "integrity": "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==" }, + "@dotenvx/dotenvx@1.75.1": { + "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==", + "dependencies": [ + "@dotenvx/primitives", + "commander@11.1.0", + "conf", + "dotenv", + "enquirer", + "env-paths", + "execa@5.1.1", + "fdir", + "ignore@5.3.2", + "object-treeify", + "open@8.4.2", + "picomatch@4.0.7", + "systeminformation", + "undici", + "which@4.0.0", + "yocto-spinner" + ], + "bin": true + }, + "@dotenvx/primitives@0.8.0": { + "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==" + }, "@earendil-works/chord@0.87.1": { "integrity": "sha512-bg7IkJGFcEaMqqYgOGUiq5Ky9RghpRfrlZ8I/v/1b4bBZ02A7t3E+6uhPRbadwWb/kWsnVFbZsqOKRN4a3LLCg==", "dependencies": [ @@ -608,7 +641,7 @@ "@earendil-works/pi-ai", "@earendil-works/pi-telemetry", "diff", - "ignore", + "ignore@7.0.8", "typebox", "yaml@2.9.0" ] @@ -828,14 +861,56 @@ "@hono/zod-validator", "hono", "openapi3-ts", - "zod" + "zod@4.6.5" ] }, "@hono/zod-validator@0.9.1_hono@4.13.9_zod@4.6.5": { "integrity": "sha512-iiv6w0qrIc0arfvCtUqBWsvl4fXjzaTcQcCJTTtCnkawF9HHGE+KjEl0ox3gQJ6rKZEgE3mLExlQCF72M+mNuw==", "dependencies": [ "hono", - "zod" + "zod@4.6.5" + ] + }, + "@inquirer/ansi@2.0.8": { + "integrity": "sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==" + }, + "@inquirer/confirm@6.3.2_@types+node@24.19.0": { + "integrity": "sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==", + "dependencies": [ + "@inquirer/core", + "@inquirer/type", + "@types/node" + ], + "optionalPeers": [ + "@types/node" + ] + }, + "@inquirer/core@12.0.3_@types+node@24.19.0": { + "integrity": "sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==", + "dependencies": [ + "@inquirer/ansi", + "@inquirer/figures", + "@inquirer/type", + "@types/node", + "cli-width", + "fast-wrap-ansi", + "mute-stream", + "signal-exit@4.1.0" + ], + "optionalPeers": [ + "@types/node" + ] + }, + "@inquirer/figures@2.0.9": { + "integrity": "sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==" + }, + "@inquirer/type@4.1.1_@types+node@24.19.0": { + "integrity": "sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==", + "dependencies": [ + "@types/node" + ], + "optionalPeers": [ + "@types/node" ] }, "@jridgewell/gen-mapping@0.3.13": { @@ -876,7 +951,7 @@ "dependencies": [ "@hono/node-server", "ajv", - "ajv-formats", + "ajv-formats@3.0.1_ajv@8.20.0", "content-type@1.0.5", "cors", "cross-spawn", @@ -886,13 +961,24 @@ "express-rate-limit", "hono", "jose", - "json-schema-typed", + "json-schema-typed@8.0.2", "pkce-challenge", "raw-body", - "zod", + "zod@4.6.5", "zod-to-json-schema" ] }, + "@mswjs/interceptors@0.41.9": { + "integrity": "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==", + "dependencies": [ + "@open-draft/deferred-promise@2.2.0", + "@open-draft/logger", + "@open-draft/until", + "is-node-process", + "outvariant", + "strict-event-emitter" + ] + }, "@napi-rs/lzma-linux-x64-gnu@1.5.1": { "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", "os": ["linux"], @@ -901,6 +987,39 @@ "@nodable/entities@3.0.0": { "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==" }, + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": [ + "@nodelib/fs.stat", + "run-parallel" + ] + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": [ + "@nodelib/fs.scandir", + "fastq" + ] + }, + "@open-draft/deferred-promise@2.2.0": { + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==" + }, + "@open-draft/deferred-promise@3.0.0": { + "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==" + }, + "@open-draft/logger@0.3.0": { + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dependencies": [ + "is-node-process", + "outvariant" + ] + }, + "@open-draft/until@2.1.0": { + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==" + }, "@peculiar/asn1-android@2.9.5": { "integrity": "sha512-KnNE4l033gbJ3ZzfYk7fJpSZwhd5xT+dPAnrB8bcwqylHiLN2zjZ0OyFao0LDIOI/ZdXRabV0n3Ww/KvpgZq6w==", "dependencies": [ @@ -2131,23 +2250,6 @@ "typescript" ] }, - "@reduxjs/toolkit@2.12.0_react@19.3.0_react-redux@9.3.0_@types+react@19.3.0": { - "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", - "dependencies": [ - "@standard-schema/spec", - "@standard-schema/utils", - "immer", - "react", - "react-redux", - "redux", - "redux-thunk", - "reselect" - ], - "optionalPeers": [ - "react", - "react-redux" - ] - }, "@remix-run/node-fetch-server@0.9.0": { "integrity": "sha512-SoLMv7dbH+njWzXnOY6fI08dFMI5+/dQ+vY3n8RnnbdG7MdJEgiP28Xj/xWlnRnED/aB6SFw56Zop+LbmaaKqA==" }, @@ -2276,8 +2378,8 @@ "os": ["win32"], "cpu": ["x64"] }, - "@simplewebauthn/browser@13.3.0": { - "integrity": "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==" + "@sec-ant/readable-stream@0.4.1": { + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==" }, "@simplewebauthn/server@13.3.3": { "integrity": "sha512-LelX/lcy5cjc15A86i/aNxHhB5eU7dd20QsbP0VLAf9e38+SLlsnqCCyecx3xqfGofhmX05h1J9fKRYWxw+luA==", @@ -2292,6 +2394,9 @@ "@peculiar/x509" ] }, + "@sindresorhus/merge-streams@4.0.0": { + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==" + }, "@smithy/core@3.35.0": { "integrity": "sha512-zRMhfkByhT2snNdr1si24vJitU6Cr9ix2MikUfWmkAgp4jrNP0GcKSP5YvwQ+TlI8AZXER5QOGJn3JsVtSD9/A==", "dependencies": [ @@ -2340,12 +2445,6 @@ "@stablelib/base64@1.0.1": { "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==" }, - "@standard-schema/spec@1.1.0": { - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" - }, - "@standard-schema/utils@0.3.0": { - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" - }, "@tailwindcss/node@4.3.3": { "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "dependencies": [ @@ -2453,6 +2552,14 @@ "react" ] }, + "@ts-morph/common@0.27.0": { + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dependencies": [ + "fast-glob", + "minimatch", + "path-browserify" + ] + }, "@types/chai@5.2.3": { "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dependencies": [ @@ -2460,42 +2567,6 @@ "assertion-error" ] }, - "@types/d3-array@3.2.2": { - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" - }, - "@types/d3-color@3.1.3": { - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" - }, - "@types/d3-ease@3.0.2": { - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" - }, - "@types/d3-interpolate@3.0.4": { - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "dependencies": [ - "@types/d3-color" - ] - }, - "@types/d3-path@3.1.1": { - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" - }, - "@types/d3-scale@4.0.9": { - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "dependencies": [ - "@types/d3-time" - ] - }, - "@types/d3-shape@3.2.0": { - "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", - "dependencies": [ - "@types/d3-path" - ] - }, - "@types/d3-time@3.0.4": { - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" - }, - "@types/d3-timer@3.0.2": { - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" - }, "@types/deep-eql@4.0.2": { "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==" }, @@ -2534,8 +2605,17 @@ "@types/retry@0.12.0": { "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, - "@types/use-sync-external-store@0.0.6": { - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + "@types/set-cookie-parser@2.4.10": { + "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", + "dependencies": [ + "@types/node" + ] + }, + "@types/statuses@2.0.6": { + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==" + }, + "@types/validate-npm-package-name@4.0.2": { + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==" }, "@typescript/typescript-aix-ppc64@7.0.2": { "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", @@ -2672,6 +2752,15 @@ "agent-base@9.0.0": { "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==" }, + "ajv-formats@2.1.1_ajv@8.20.0": { + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": [ + "ajv" + ], + "optionalPeers": [ + "ajv" + ] + }, "ajv-formats@3.0.1_ajv@8.20.0": { "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dependencies": [ @@ -2690,12 +2779,33 @@ "require-from-string" ] }, + "ansi-colors@4.1.3": { + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-regex@6.4.0": { + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==" + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "ansis@4.4.0": { + "integrity": "sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==" + }, "anynum@1.0.1": { "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==" }, "arg@5.0.2": { "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, + "argparse@2.0.1": { + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "aria-hidden@1.2.6": { "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "dependencies": [ @@ -2716,9 +2826,18 @@ "assertion-error@2.0.1": { "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==" }, + "ast-types@0.16.3": { + "integrity": "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==", + "dependencies": [ + "tslib@2.8.1" + ] + }, "atomic-sleep@1.0.0": { "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "atomically@1.7.0": { + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==" + }, "babel-dead-code-elimination@1.0.12": { "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", "dependencies": [ @@ -2728,6 +2847,9 @@ "@babel/types" ] }, + "balanced-match@4.0.4": { + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, "base64-js@1.5.1": { "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, @@ -2758,6 +2880,18 @@ "bowser@2.14.1": { "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" }, + "brace-expansion@5.0.12": { + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dependencies": [ + "balanced-match" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, "browserslist@4.29.1": { "integrity": "sha512-AUdjuRyCNGUYtqpqfTmWyM4fXay8yIQhmLnvYe/THMGfT9B/34X7xQd3ifKxwyNPPpowVBjLb+64BN9Rn1mizw==", "dependencies": [ @@ -2772,6 +2906,12 @@ "buffer-equal-constant-time@1.0.1": { "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "bundle-name@4.1.1": { + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dependencies": [ + "run-applescript" + ] + }, "bytes@3.1.2": { "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, @@ -2792,12 +2932,18 @@ "get-intrinsic" ] }, + "callsites@3.1.0": { + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, "caniuse-lite@1.0.30001812": { "integrity": "sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==" }, "chai@6.2.2": { "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==" }, + "chalk@5.6.2": { + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==" + }, "cheerio-select@2.1.0": { "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "dependencies": [ @@ -2837,15 +2983,68 @@ "clsx" ] }, + "cli-cursor@5.0.0": { + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dependencies": [ + "restore-cursor" + ] + }, + "cli-spinners@2.9.2": { + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==" + }, + "cli-width@4.1.0": { + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==" + }, + "cliui@8.0.1": { + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": [ + "string-width@4.2.3", + "strip-ansi@6.0.1", + "wrap-ansi" + ] + }, "clsx@2.1.1": { "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" }, + "code-block-writer@13.0.3": { + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==" + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "colorette@2.0.20": { "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "commander@11.1.0": { + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==" + }, + "commander@14.0.3": { + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==" + }, "commander@5.1.0": { "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" }, + "conf@10.2.0": { + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "dependencies": [ + "ajv", + "ajv-formats@2.1.1_ajv@8.20.0", + "atomically", + "debounce-fn", + "dot-prop", + "env-paths", + "json-schema-typed@7.0.3", + "onetime@5.1.2", + "pkg-up", + "semver@7.8.5" + ] + }, "confbox@0.3.1": { "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==" }, @@ -2877,12 +3076,25 @@ "vary" ] }, + "cosmiconfig@9.0.2_typescript@7.0.2": { + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dependencies": [ + "env-paths", + "import-fresh", + "js-yaml", + "parse-json", + "typescript" + ], + "optionalPeers": [ + "typescript" + ] + }, "cross-spawn@7.0.6": { "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": [ - "path-key", + "path-key@3.1.1", "shebang-command", - "which" + "which@2.0.2" ] }, "css-select@5.2.2": { @@ -2898,64 +3110,13 @@ "css-what@6.2.2": { "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==" }, + "cssesc@3.0.0": { + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": true + }, "csstype@3.2.3": { "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, - "d3-array@3.2.4": { - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dependencies": [ - "internmap" - ] - }, - "d3-color@3.1.0": { - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" - }, - "d3-ease@3.0.1": { - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" - }, - "d3-format@3.1.2": { - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==" - }, - "d3-interpolate@3.0.1": { - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": [ - "d3-color" - ] - }, - "d3-path@3.1.0": { - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" - }, - "d3-scale@4.0.2": { - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dependencies": [ - "d3-array", - "d3-format", - "d3-interpolate", - "d3-time", - "d3-time-format" - ] - }, - "d3-shape@3.2.0": { - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dependencies": [ - "d3-path" - ] - }, - "d3-time-format@4.1.0": { - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dependencies": [ - "d3-time" - ] - }, - "d3-time@3.1.0": { - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dependencies": [ - "d3-array" - ] - }, - "d3-timer@3.0.1": { - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" - }, "data-uri-to-buffer@4.0.1": { "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, @@ -2965,18 +3126,40 @@ "dateformat@4.6.3": { "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" }, + "debounce-fn@4.0.0": { + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "dependencies": [ + "mimic-fn@3.1.0" + ] + }, "debug@4.4.3": { "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": [ "ms" ] }, - "decimal.js-light@2.5.1": { - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" - }, "dedent@1.7.2": { "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==" }, + "deepmerge@4.3.1": { + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "default-browser-id@5.0.1": { + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==" + }, + "default-browser@5.5.1": { + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dependencies": [ + "bundle-name", + "default-browser-id" + ] + }, + "define-lazy-prop@2.0.0": { + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-lazy-prop@3.0.0": { + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==" + }, "depd@2.0.0": { "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, @@ -3014,6 +3197,15 @@ "domhandler" ] }, + "dot-prop@6.0.1": { + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": [ + "is-obj@2.0.0" + ] + }, + "dotenv@17.4.2": { + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==" + }, "dunder-proto@1.0.1": { "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dependencies": [ @@ -3034,6 +3226,12 @@ "electron-to-chromium@1.5.439": { "integrity": "sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==" }, + "emoji-regex@10.6.0": { + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==" + }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, @@ -3057,6 +3255,13 @@ "tapable" ] }, + "enquirer@2.4.1": { + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dependencies": [ + "ansi-colors", + "strip-ansi@6.0.1" + ] + }, "entities@4.5.0": { "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" }, @@ -3066,6 +3271,15 @@ "entities@7.0.1": { "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" }, + "env-paths@2.2.1": { + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" + }, + "error-ex@1.3.4": { + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dependencies": [ + "is-arrayish" + ] + }, "es-define-property@1.0.1": { "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, @@ -3084,9 +3298,6 @@ "es-errors" ] }, - "es-toolkit@1.52.0": { - "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==" - }, "esbuild@0.28.2": { "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "optionalDependencies": [ @@ -3126,6 +3337,10 @@ "escape-html@1.0.3": { "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "esprima@4.0.1": { + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": true + }, "estree-walker@3.0.3": { "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dependencies": [ @@ -3135,9 +3350,6 @@ "etag@1.8.1": { "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, - "eventemitter3@5.0.4": { - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==" - }, "eventsource-parser@3.1.1": { "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==" }, @@ -3147,6 +3359,37 @@ "eventsource-parser" ] }, + "execa@5.1.1": { + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": [ + "cross-spawn", + "get-stream@6.0.1", + "human-signals@2.1.0", + "is-stream@2.0.1", + "merge-stream", + "npm-run-path@4.0.1", + "onetime@5.1.2", + "signal-exit@3.0.7", + "strip-final-newline@2.0.0" + ] + }, + "execa@9.6.1": { + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dependencies": [ + "@sindresorhus/merge-streams", + "cross-spawn", + "figures", + "get-stream@9.0.1", + "human-signals@8.0.1", + "is-plain-obj", + "is-stream@4.0.1", + "npm-run-path@6.0.0", + "pretty-ms", + "signal-exit@4.1.0", + "strip-final-newline@4.0.0", + "yoctocolors" + ] + }, "exit-hook@2.2.1": { "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==" }, @@ -3206,15 +3449,40 @@ "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob@3.3.3": { + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": [ + "@nodelib/fs.stat", + "@nodelib/fs.walk", + "glob-parent", + "merge2", + "micromatch" + ] + }, "fast-safe-stringify@2.1.1": { "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "fast-sha256@1.3.0": { "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==" }, + "fast-string-truncated-width@3.0.3": { + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==" + }, + "fast-string-width@3.0.2": { + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dependencies": [ + "fast-string-truncated-width" + ] + }, "fast-uri@3.1.8": { "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==" }, + "fast-wrap-ansi@0.2.2": { + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dependencies": [ + "fast-string-width" + ] + }, "fast-xml-builder@1.3.1": { "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", "dependencies": [ @@ -3234,13 +3502,19 @@ ], "bin": true }, + "fastq@1.20.3": { + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dependencies": [ + "reusify" + ] + }, "fdir@6.5.0_picomatch@4.0.7": { "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dependencies": [ - "picomatch" + "picomatch@4.0.7" ], "optionalPeers": [ - "picomatch" + "picomatch@4.0.7" ] }, "fetch-blob@3.2.0": { @@ -3250,6 +3524,18 @@ "web-streams-polyfill" ] }, + "figures@6.1.0": { + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dependencies": [ + "is-unicode-supported@2.1.0" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, "finalhandler@2.1.1": { "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dependencies": [ @@ -3261,6 +3547,12 @@ "statuses" ] }, + "find-up@3.0.0": { + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": [ + "locate-path" + ] + }, "formdata-polyfill@4.0.10": { "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dependencies": [ @@ -3273,6 +3565,14 @@ "fresh@2.0.0": { "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" }, + "fs-extra@11.4.1": { + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dependencies": [ + "graceful-fs", + "jsonfile", + "universalify" + ] + }, "fsevents@2.3.3": { "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "os": ["darwin"], @@ -3281,6 +3581,12 @@ "function-bind@1.1.2": { "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, + "fuzzysort@3.1.0": { + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==" + }, + "fzf@0.5.2": { + "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==" + }, "gaxios@7.3.1": { "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", "dependencies": [ @@ -3300,6 +3606,12 @@ "gensync@1.0.0-beta.2": { "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, + "get-caller-file@2.0.5": { + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-east-asian-width@1.7.0": { + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==" + }, "get-intrinsic@1.3.0": { "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": [ @@ -3318,6 +3630,9 @@ "get-nonce@1.0.1": { "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" }, + "get-own-enumerable-keys@1.0.0": { + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==" + }, "get-proto@1.0.1": { "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": [ @@ -3325,6 +3640,22 @@ "es-object-atoms" ] }, + "get-stream@6.0.1": { + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "get-stream@9.0.1": { + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dependencies": [ + "@sec-ant/readable-stream", + "is-stream@4.0.1" + ] + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, "globrex@0.1.2": { "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" }, @@ -3348,6 +3679,9 @@ "graceful-fs@4.2.11": { "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "graphql@16.14.2": { + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==" + }, "has-symbols@1.1.0": { "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, @@ -3360,6 +3694,13 @@ "function-bind" ] }, + "headers-polyfill@5.0.1": { + "integrity": "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==", + "dependencies": [ + "@types/set-cookie-parser", + "set-cookie-parser@3.1.2" + ] + }, "help-me@5.0.0": { "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" }, @@ -3408,6 +3749,12 @@ "proxy-agent-negotiate" ] }, + "human-signals@2.1.0": { + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" + }, + "human-signals@8.0.1": { + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==" + }, "iconv-lite@0.6.3": { "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": [ @@ -3420,36 +3767,121 @@ "safer-buffer" ] }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, "ignore@7.0.8": { "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==" }, - "immer@11.1.18": { - "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==" + "import-fresh@3.3.1": { + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": [ + "parent-module", + "resolve-from" + ] }, "inherits@2.0.4": { "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "internmap@2.0.3": { - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" - }, "ip-address@10.7.2": { "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==" }, "ipaddr.js@1.9.1": { "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-arrayish@0.2.1": { + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-docker@2.2.1": { + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": true + }, + "is-docker@3.0.0": { + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": true + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-in-ssh@1.0.0": { + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==" + }, + "is-inside-container@1.0.0": { + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": [ + "is-docker@3.0.0" + ], + "bin": true + }, + "is-interactive@2.0.0": { + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==" + }, + "is-node-process@1.2.0": { + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-obj@2.0.0": { + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-obj@3.0.0": { + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" + }, + "is-plain-obj@4.1.0": { + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + }, "is-promise@4.0.0": { "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "is-regexp@3.1.0": { + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==" + }, + "is-stream@2.0.1": { + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "is-stream@4.0.1": { + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==" + }, + "is-unicode-supported@1.3.0": { + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==" + }, + "is-unicode-supported@2.1.0": { + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==" + }, "is-unsafe@2.0.2": { "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==" }, + "is-wsl@2.2.0": { + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": [ + "is-docker@2.2.1" + ] + }, + "is-wsl@3.1.1": { + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dependencies": [ + "is-inside-container" + ] + }, "isbot@5.2.2": { "integrity": "sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==" }, "isexe@2.0.0": { "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "isexe@3.1.5": { + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==" + }, "jiti@2.7.0": { "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "bin": true @@ -3466,6 +3898,13 @@ "js-tokens@4.0.0": { "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "js-yaml@4.3.2": { + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dependencies": [ + "argparse" + ], + "bin": true + }, "jsesc@3.0.2": { "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": true @@ -3476,6 +3915,9 @@ "bignumber.js" ] }, + "json-parse-even-better-errors@2.3.1": { + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "json-schema-to-ts@3.1.1": { "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", "dependencies": [ @@ -3486,6 +3928,9 @@ "json-schema-traverse@1.0.0": { "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "json-schema-typed@7.0.3": { + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==" + }, "json-schema-typed@8.0.2": { "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" }, @@ -3493,6 +3938,15 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": true }, + "jsonfile@6.2.1": { + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dependencies": [ + "universalify" + ], + "optionalDependencies": [ + "graceful-fs" + ] + }, "jwa@2.0.1": { "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dependencies": [ @@ -3508,6 +3962,12 @@ "safe-buffer" ] }, + "kleur@3.0.3": { + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "kleur@4.1.5": { + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" + }, "kysely@0.29.6": { "integrity": "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg==" }, @@ -3585,9 +4045,26 @@ "lightningcss-win32-x64-msvc" ] }, + "lines-and-columns@1.2.4": { + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "locate-path@3.0.0": { + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": [ + "p-locate", + "path-exists" + ] + }, "lodash@4.18.1": { "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, + "log-symbols@6.0.0": { + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dependencies": [ + "chalk", + "is-unicode-supported@1.3.0" + ] + }, "long@5.3.2": { "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, @@ -3624,6 +4101,19 @@ "merge-descriptors@2.0.0": { "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" }, + "merge-stream@2.0.0": { + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2@1.4.1": { + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch@2.3.2" + ] + }, "mime-db@1.54.0": { "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" }, @@ -3633,12 +4123,59 @@ "mime-db" ] }, + "mimic-fn@2.1.0": { + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mimic-fn@3.1.0": { + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + }, + "mimic-function@5.0.1": { + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==" + }, + "minimatch@10.2.6": { + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dependencies": [ + "brace-expansion" + ] + }, "minimist@1.2.8": { "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "msw@2.15.0_typescript@7.0.2_@types+node@24.19.0": { + "integrity": "sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==", + "dependencies": [ + "@inquirer/confirm", + "@mswjs/interceptors", + "@open-draft/deferred-promise@3.0.0", + "@types/statuses", + "cookie@1.1.1", + "graphql", + "headers-polyfill", + "is-node-process", + "outvariant", + "path-to-regexp@6.3.0", + "picocolors", + "rettime", + "statuses", + "strict-event-emitter", + "tough-cookie", + "type-fest", + "typescript", + "until-async", + "yargs" + ], + "optionalPeers": [ + "typescript" + ], + "scripts": true, + "bin": true + }, + "mute-stream@3.0.0": { + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==" + }, "nanoid@3.3.19": { "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "bin": true @@ -3671,6 +4208,19 @@ "node-releases@2.0.57": { "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==" }, + "npm-run-path@4.0.1": { + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": [ + "path-key@3.1.1" + ] + }, + "npm-run-path@6.0.0": { + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dependencies": [ + "path-key@4.0.0", + "unicorn-magic" + ] + }, "nth-check@2.1.1": { "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dependencies": [ @@ -3682,7 +4232,7 @@ "dependencies": [ "a-sync-waterfall", "asap", - "commander" + "commander@5.1.0" ], "bin": true }, @@ -3692,6 +4242,9 @@ "object-inspect@1.13.4": { "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, + "object-treeify@1.1.33": { + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==" + }, "obug@2.2.1": { "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==" }, @@ -3710,13 +4263,44 @@ "wrappy" ] }, + "onetime@5.1.2": { + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": [ + "mimic-fn@2.1.0" + ] + }, + "onetime@7.0.0": { + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dependencies": [ + "mimic-function" + ] + }, + "open@11.0.4": { + "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", + "dependencies": [ + "default-browser", + "define-lazy-prop@3.0.0", + "is-in-ssh", + "is-inside-container", + "powershell-utils@0.2.1", + "wsl-utils" + ] + }, + "open@8.4.2": { + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": [ + "define-lazy-prop@2.0.0", + "is-docker@2.2.1", + "is-wsl@2.2.0" + ] + }, "openai@6.40.0_zod@4.6.5": { "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==", "dependencies": [ - "zod" + "zod@4.6.5" ], "optionalPeers": [ - "zod" + "zod@4.6.5" ] }, "openapi3-ts@4.6.1": { @@ -3734,6 +4318,35 @@ "postgres" ] }, + "ora@8.2.0": { + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dependencies": [ + "chalk", + "cli-cursor", + "cli-spinners", + "is-interactive", + "is-unicode-supported@2.1.0", + "log-symbols", + "stdin-discarder", + "string-width@7.2.0", + "strip-ansi@7.2.0" + ] + }, + "outvariant@1.4.3": { + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==" + }, + "p-limit@2.3.0": { + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": [ + "p-try" + ] + }, + "p-locate@3.0.0": { + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": [ + "p-limit" + ] + }, "p-map@7.0.8": { "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==" }, @@ -3744,6 +4357,30 @@ "retry" ] }, + "p-try@2.2.0": { + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-manager-detector@1.8.0": { + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==" + }, + "parent-module@1.0.1": { + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": [ + "callsites" + ] + }, + "parse-json@5.2.0": { + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": [ + "@babel/code-frame", + "error-ex", + "json-parse-even-better-errors", + "lines-and-columns" + ] + }, + "parse-ms@4.0.0": { + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==" + }, "parse5-htmlparser2-tree-adapter@7.1.0": { "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "dependencies": [ @@ -3769,12 +4406,24 @@ "partial-json@0.1.7": { "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==" }, + "path-browserify@1.0.1": { + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, + "path-exists@3.0.0": { + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + }, "path-expression-matcher@1.6.2": { "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==" }, "path-key@3.1.1": { "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "path-key@4.0.0": { + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==" + }, + "path-to-regexp@6.3.0": { + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" + }, "path-to-regexp@8.4.2": { "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" }, @@ -3834,6 +4483,9 @@ "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, + "picomatch@2.3.2": { + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" + }, "picomatch@4.0.7": { "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==" }, @@ -3893,10 +4545,23 @@ "pathe@2.0.3" ] }, + "pkg-up@3.1.0": { + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": [ + "find-up" + ] + }, "playwright-core@1.63.0": { "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", "bin": true }, + "postcss-selector-parser@7.1.6": { + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dependencies": [ + "cssesc", + "util-deprecate" + ] + }, "postcss@8.5.28": { "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dependencies": [ @@ -3923,13 +4588,32 @@ "postgres@3.4.9": { "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==" }, + "powershell-utils@0.1.0": { + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==" + }, + "powershell-utils@0.2.1": { + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==" + }, "prettier@3.9.9": { "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "bin": true }, + "pretty-ms@9.3.1": { + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "dependencies": [ + "parse-ms" + ] + }, "process-warning@5.1.0": { "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==" }, + "prompts@2.4.2": { + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": [ + "kleur@3.0.3", + "sisteransi" + ] + }, "protobufjs@7.6.6": { "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", "dependencies": [ @@ -3973,15 +4657,6 @@ "pvutils@1.2.0": { "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==" }, - "qr-code-styling@1.9.2": { - "integrity": "sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==", - "dependencies": [ - "qrcode-generator" - ] - }, - "qrcode-generator@1.5.2": { - "integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==" - }, "qs@6.16.0": { "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dependencies": [ @@ -3989,6 +4664,9 @@ "side-channel" ] }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, "quick-format-unescaped@4.0.4": { "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, @@ -4091,23 +4769,6 @@ "scheduler" ] }, - "react-is@19.3.0": { - "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==" - }, - "react-redux@9.3.0_@types+react@19.3.0_react@19.3.0_redux@5.0.1": { - "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "dependencies": [ - "@types/react", - "@types/use-sync-external-store", - "react", - "redux", - "use-sync-external-store" - ], - "optionalPeers": [ - "@types/react", - "redux" - ] - }, "react-refresh@0.14.2": { "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==" }, @@ -4144,7 +4805,7 @@ "cookie@1.1.1", "react", "react-dom", - "set-cookie-parser" + "set-cookie-parser@2.7.2" ], "optionalPeers": [ "react-dom" @@ -4174,46 +4835,44 @@ "real-require@1.0.0": { "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==" }, - "recharts@3.10.1_react@19.3.0_react-dom@19.3.0_react-is@19.3.0_@types+react@19.3.0_redux@5.0.1": { - "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "recast@0.23.21": { + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", "dependencies": [ - "@reduxjs/toolkit", - "clsx", - "decimal.js-light", - "es-toolkit", - "eventemitter3", - "immer", - "react", - "react-dom", - "react-is", - "react-redux", - "reselect", + "ast-types", + "esprima", + "source-map", "tiny-invariant", - "use-sync-external-store", - "victory-vendor" + "tslib@2.8.1" ] }, - "redux-thunk@3.1.0_redux@5.0.1": { - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "dependencies": [ - "redux" - ] - }, - "redux@5.0.1": { - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" - }, "reflect-metadata@0.2.2": { "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, + "require-directory@2.1.1": { + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, "require-from-string@2.0.2": { "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, - "reselect@5.2.0": { - "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==" + "resolve-from@4.0.0": { + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "restore-cursor@5.1.0": { + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dependencies": [ + "onetime@7.0.0", + "signal-exit@4.1.0" + ] }, "retry@0.13.1": { "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" }, + "rettime@0.11.12": { + "integrity": "sha512-5ExtR1ppiDBQkVoPw5NAqdbfH4Iq2VOmQKHMtUoaFxA9bo2krzFYBtyq6K40xxbYp+2w/YOr6D+1ToF6tTN53A==" + }, + "reusify@1.1.0": { + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" + }, "rollup@4.63.5": { "integrity": "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==", "dependencies": [ @@ -4257,7 +4916,16 @@ "depd", "is-promise", "parseurl", - "path-to-regexp" + "path-to-regexp@8.4.2" + ] + }, + "run-applescript@7.1.0": { + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==" + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": [ + "queue-microtask" ] }, "safe-buffer@5.2.1": { @@ -4311,9 +4979,53 @@ "set-cookie-parser@2.7.2": { "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" }, + "set-cookie-parser@3.1.2": { + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==" + }, "setprototypeof@1.2.0": { "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shadcn@3.8.5_@types+node@24.19.0_typescript@7.0.2": { + "integrity": "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA==", + "dependencies": [ + "@antfu/ni", + "@babel/core", + "@babel/parser", + "@babel/plugin-transform-typescript", + "@babel/preset-typescript", + "@dotenvx/dotenvx", + "@modelcontextprotocol/sdk", + "@types/validate-npm-package-name", + "browserslist", + "commander@14.0.3", + "cosmiconfig", + "dedent", + "deepmerge", + "diff", + "execa@9.6.1", + "fast-glob", + "fs-extra", + "fuzzysort", + "https-proxy-agent@7.0.6", + "kleur@4.1.5", + "msw", + "node-fetch", + "open@11.0.4", + "ora", + "postcss", + "postcss-selector-parser", + "prompts", + "recast", + "stringify-object", + "tailwind-merge", + "ts-morph", + "tsconfig-paths", + "validate-npm-package-name", + "zod@3.25.76", + "zod-to-json-schema" + ], + "bin": true + }, "shebang-command@2.0.0": { "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dependencies": [ @@ -4359,6 +5071,15 @@ "side-channel-weakmap" ] }, + "signal-exit@3.0.7": { + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "signal-exit@4.1.0": { + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + }, + "sisteransi@1.0.5": { + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, "sonic-boom@4.2.1": { "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "dependencies": [ @@ -4379,6 +5100,9 @@ "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, + "source-map@0.6.1": { + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "split2@4.2.0": { "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" }, @@ -4395,6 +5119,57 @@ "std-env@4.2.0": { "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==" }, + "stdin-discarder@0.2.2": { + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==" + }, + "strict-event-emitter@0.5.1": { + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==" + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": [ + "emoji-regex@8.0.0", + "is-fullwidth-code-point", + "strip-ansi@6.0.1" + ] + }, + "string-width@7.2.0": { + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dependencies": [ + "emoji-regex@10.6.0", + "get-east-asian-width", + "strip-ansi@7.2.0" + ] + }, + "stringify-object@5.0.0": { + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dependencies": [ + "get-own-enumerable-keys", + "is-obj@3.0.0", + "is-regexp" + ] + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex@5.0.1" + ] + }, + "strip-ansi@7.2.0": { + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dependencies": [ + "ansi-regex@6.4.0" + ] + }, + "strip-bom@3.0.0": { + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + }, + "strip-final-newline@2.0.0": { + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, + "strip-final-newline@4.0.0": { + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==" + }, "strip-json-comments@5.0.3": { "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==" }, @@ -4404,6 +5179,14 @@ "anynum" ] }, + "systeminformation@5.33.13": { + "integrity": "sha512-GtyakkchTQKymM1WeAB652zi3rM1q00RKdc+jZe9KGstuc/mWUwAnQGN/RE5gVYvHIR8xYOUIUD9irVKDtA0wA==", + "os": ["darwin", "linux", "win32", "freebsd", "openbsd", "netbsd", "sunos", "android"], + "bin": true + }, + "tagged-tag@1.0.0": { + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" + }, "tailwind-merge@3.7.0": { "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==" }, @@ -4432,15 +5215,44 @@ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dependencies": [ "fdir", - "picomatch" + "picomatch@4.0.7" + ] + }, + "tldts-core@7.4.15": { + "integrity": "sha512-ERuv0p98XgSzmlSLJr8vDNxX+uATGInlN97V3R+JpYWaSqn5IG3ewWgCwczk4bkOpgth/o8Nt5FOi4B4w0n/1A==" + }, + "tldts@7.4.15": { + "integrity": "sha512-SJVBeHOxDbNoq14CvpAoA2mLEWdbldGK8nR+yumpjY5nrlZxOflcA7p1Qj1gbTGmbu9DY9qLj/bENSWhBzjxRQ==", + "dependencies": [ + "tldts-core" + ], + "bin": true + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" ] }, "toidentifier@1.0.1": { "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "tough-cookie@6.0.2": { + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dependencies": [ + "tldts" + ] + }, "ts-algebra@2.0.0": { "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==" }, + "ts-morph@26.0.0": { + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dependencies": [ + "@ts-morph/common", + "code-block-writer" + ] + }, "tsconfck@3.1.6_typescript@7.0.2": { "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", "dependencies": [ @@ -4452,6 +5264,14 @@ "deprecated": true, "bin": true }, + "tsconfig-paths@4.2.0": { + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dependencies": [ + "json5", + "minimist", + "strip-bom" + ] + }, "tslib@1.14.1": { "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, @@ -4467,6 +5287,12 @@ "tw-animate-css@1.4.0": { "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==" }, + "type-fest@5.10.0": { + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "dependencies": [ + "tagged-tag" + ] + }, "type-is@2.1.0": { "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dependencies": [ @@ -4510,9 +5336,18 @@ "undici@7.30.0": { "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==" }, + "unicorn-magic@0.3.0": { + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==" + }, + "universalify@2.0.1": { + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + }, "unpipe@1.0.0": { "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, + "until-async@3.0.2": { + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==" + }, "update-browserslist-db@1.3.3_browserslist@4.29.1": { "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", "dependencies": [ @@ -4545,11 +5380,8 @@ "@types/react" ] }, - "use-sync-external-store@1.7.0_react@19.3.0": { - "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", - "dependencies": [ - "react" - ] + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "valibot@1.5.0_typescript@7.0.2": { "integrity": "sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==", @@ -4560,28 +5392,12 @@ "typescript" ] }, + "validate-npm-package-name@7.0.2": { + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==" + }, "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, - "victory-vendor@37.3.6": { - "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", - "dependencies": [ - "@types/d3-array", - "@types/d3-ease", - "@types/d3-interpolate", - "@types/d3-scale", - "@types/d3-shape", - "@types/d3-time", - "@types/d3-timer", - "d3-array", - "d3-ease", - "d3-interpolate", - "d3-scale", - "d3-shape", - "d3-time", - "d3-timer" - ] - }, "vite-node@3.2.4_@types+node@24.19.0_yaml@2.9.1": { "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dependencies": [ @@ -4611,7 +5427,7 @@ "@types/node", "esbuild", "fdir", - "picomatch", + "picomatch@4.0.7", "postcss", "rollup", "tinyglobby", @@ -4637,7 +5453,7 @@ "expect-type", "magic-string@1.4.2", "obug", - "picomatch", + "picomatch@4.0.7", "std-env", "tinybench", "tinyexec", @@ -4666,7 +5482,14 @@ "which@2.0.2": { "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ - "isexe" + "isexe@2.0.0" + ], + "bin": true + }, + "which@4.0.0": { + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dependencies": [ + "isexe@3.1.5" ], "bin": true }, @@ -4674,12 +5497,27 @@ "integrity": "sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A==", "bin": true }, + "wrap-ansi@7.0.0": { + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": [ + "ansi-styles", + "string-width@4.2.3", + "strip-ansi@6.0.1" + ] + }, "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws@8.21.3": { "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==" }, + "wsl-utils@1.0.0": { + "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", + "dependencies": [ + "is-wsl@3.1.1", + "powershell-utils@0.1.0" + ] + }, "xml-naming@0.3.0": { "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==" }, @@ -4689,6 +5527,9 @@ "xtend@4.0.2": { "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, + "y18n@5.0.8": { + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, "yallist@3.1.1": { "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, @@ -4700,12 +5541,39 @@ "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "bin": true }, + "yargs-parser@21.1.1": { + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yargs@17.7.3": { + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dependencies": [ + "cliui", + "escalade", + "get-caller-file", + "require-directory", + "string-width@4.2.3", + "y18n", + "yargs-parser" + ] + }, + "yocto-spinner@1.2.2": { + "integrity": "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==", + "dependencies": [ + "yoctocolors" + ] + }, + "yoctocolors@2.2.0": { + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==" + }, "zod-to-json-schema@3.25.2_zod@4.6.5": { "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "dependencies": [ - "zod" + "zod@4.6.5" ] }, + "zod@3.25.76": { + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" + }, "zod@4.6.5": { "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==" } @@ -4751,7 +5619,6 @@ "npm:@fontsource-variable/space-grotesk@^5.2.10", "npm:@react-router/dev@7.12.0", "npm:@react-router/node@7.12.0", - "npm:@simplewebauthn/browser@^13.3.0", "npm:@tailwindcss/vite@^4.1.13", "npm:@tanstack/react-query@^5.90.21", "npm:@types/react-dom@^19.2.3", @@ -4762,13 +5629,12 @@ "npm:isbot@^5.1.31", "npm:lucide-react@0.576", "npm:next-themes@~0.4.6", - "npm:qr-code-styling@^1.9.2", "npm:radix-ui@^1.4.3", "npm:react-day-picker@^10.0.1", "npm:react-dom@^19.2.4", "npm:react-router@7.12.0", "npm:react@^19.2.4", - "npm:recharts@^3.10.1", + "npm:shadcn@^3.8.5", "npm:sonner@^2.0.7", "npm:tailwind-merge@^3.5.0", "npm:tailwindcss@^4.1.13", diff --git a/docs/runbook/browser.md b/docs/runbook/browser.md index 447fefdb..722c629e 100644 --- a/docs/runbook/browser.md +++ b/docs/runbook/browser.md @@ -293,6 +293,9 @@ pixelflux + labwc Wayland 桌面 + Selkies 2.0 Web 客户端)+ camofox 1.17.0 ** 原站视图 Tab 调 `GET /api/v1/browser/desktop/{sessionId}/_status` 决定模式:`available`(容器 3000 能连上且有访问凭据) 且 `window.isSecureContext` → 桌面模式(iframe);否则截图模式,顶部写原因:"桌面模式不可用:…" 或 "桌面模式需要通过 HTTPS 访问控制台"。 桌面模式顶栏:连接状态(已连接,只读 / 可操作)、当前控制者、接管 / 交还(沿用 `control/acquire|release`)、重新连接、截图模式、关闭会话。 +「当前控制」(HH-2,两种模式共用 `coords.ts controllerLabel`):`controller = automation` 且 2 分钟内有过自动化动作(Agent 能力经 +`BrowserService.run` 成功执行,写 `recovery_state.lastAutomationAt`,会话视图字段 `lastAutomationAt`)才显示「Agent」,说明写 +「Agent 正在操作」;否则显示「自动化」,说明写「目前没有 Agent 在操作」。人工接管显示「人工接管(用户名)」,人工动作不刷新该时间。 接管后直接在画面里点和输入;中文、日文用**本机输入法**(Selkies 接收 composition 事件,按 Unicode 注入上屏文本); 复制粘贴用 Ctrl+C / Ctrl+V。 diff --git a/docs/runbook/sites.md b/docs/runbook/sites.md index 09df0ba6..e2523045 100644 --- a/docs/runbook/sites.md +++ b/docs/runbook/sites.md @@ -54,6 +54,10 @@ - **站点内添加**(站点详情「快照与 Feed」→ 新建 Feed,`components/sites/site-feed-dialog.tsx`):只填地址、用途(发现 = `generic` / 追踪 = `search` / 促销 = `promotion`,缺省发现)、可选备注(即名称)。地址输入时下方预览脱敏后的显示值;域名与站点不符给黄色提示但允许保存。保存后立即 `POST /feed-sources/{id}/fetch` 一次,对话框每 2 s 轮询 `GET /feed-sources/{id}`(最多 90 s),显示首抓条目数(游标窗口 `lastGuidSet` 长度)或 `lastError`。 - **`POST /feed-sources` 带 `siteId` 时的推断**(`service/feeds.ts feedDefaultsForSite`):`siteAccountId` 缺省取该站启用账号,优先带 passkey 的,再取最早建的;`parserVersion` 记为 `rss@1;<站点定义 umt.version>`(解析仍走通用 RSS 解析器,版本只作追溯);`name` 缺省「站点名 RSS」;`pollIntervalSec` 缺省 1800(站点请求克制;不挂站点的 Feed 仍缺省 900)。地址主机键(去 `www.`、取最后两级,IP 原样)与站点 `baseUrl` / 定义 `domain` 都不一致时,响应附 `warnings[]`(只在创建响应出现)。`name` 现在可省,不挂站点时缺省为地址主机名。 +- **站点 RSS 条目规范化(HH-2,`packages/adapters/src/sites/feed-item.ts normalizeSiteFeedItem`)**:NexusPHP `torrentrss.php` 的标题是 `[分类]种子名[副标题][大小][上传者]`(憨憨实测 `[音乐]Travis - L.A. Times (2024) - WEB-DL - 24bit ALAC-HHWEB[Travis - L.A. Times | ALAC | …][697.64 MB]`)。挂站点的 Feed 入库时按站点定义拆开:前缀只认定义里登记过的分类名(`umt.categoryNames` / `category.*.desc`),换成站点分类 ID 写 `category_raw`(与列表快照一致,资源页分类筛选可用);尾部配平地剥最多 3 段方括号,只有出现大小段时才把它前面一段当副标题、后面一段当上传者;去掉「[新]」。大小优先用 enclosure `length`(精确字节),没有才用标题里的显示值。`source_release.raw.rss` 记 `rawTitle / categoryName / mediaType(movie·tv·music·null) / uploader / v`。不改标题解析器(`[分类]` 前缀引起的误识别归 ID-2)。 +- **与列表快照合并**:同站同资源 ID 一行。快照见过的行(`raw._site`)标题 / 副标题 / 分类 / 做种 / 促销 / H&R 以快照为准,RSS 只补空缺、更新 `raw.rss`;快照没见过的行,RSS 内容变化时用新值。快照后到时照常覆盖 RSS 值。 +- **旧行补规范化**:规范化上线前写入的 RSS 行(`raw.rss.v` 缺失)在该站任一 Feed 下次抓取时按原标题重算(每次最多 500 行,不请求站点);标题变了且未锁定的重新发 `release.observed`(幂等键 `…:rss-norm-1`)。 +- **资源列表展示**:列表按 `coalesce(发布时间, 首次发现)` 倒序,和字段是否齐全无关。RSS 独有的条目(憨憨的音乐区不在 `torrents.php` 快照里)没有做种 / 下载 / 完成、H&R,列表显示「—」,不挂「H&R 未知」徽章(详情抽屉的 H&R 字段仍写「未知」);分类名取快照分类名 → `raw.rss.categoryName` → 分类 ID。 - **来源页任意 RSS**(`components/workbench/feed-form.tsx`):完整表单不变;新建时地址域名匹配已登记站点会自动选中该站和账号并提示「按地址域名关联到 X · 不关联」,手动改过站点后不再自动改。 ## 定时任务 @@ -89,7 +93,7 @@ 站点定义可带 `umt.defaults`(`snapshotIntervalSec / snapshotMaxPages / healthCheckIntervalSec / userDataIntervalSec / searchConcurrency / detailCacheSec`),是按站点实测给的保守下限;运行时 `siteAccessFor(def, settings.sites)` 合并:周期、缓存取较长,页数取较小;`searchConcurrency`(缺省 1)让同站不同关键词的实时补查排队执行。目前只有憨憨配置了(快照 60 分钟 1 页、健康检测 6 小时、用户数据 6 小时、搜索并发 1、详情缓存 24 小时、限流 3 秒 1 个令牌突发 2)。 -每从站点限流器拿到一个令牌(列表、搜索、详情、下载、登录、用户数据、原站视图导航)记一次请求,写 `site_request_count`(迁移 `0046`,按小时聚合,多进程共用,保留 7 天)。`GET /sites/{id}/request-stats`(read)返回最近 24 小时总数、按小时明细、快照实际周期与合并后的访问默认;站点页概览「近 24 小时请求」与资源页列表头显示。计数只在升级后开始累计。 +每从站点限流器拿到一个令牌(列表 / 快照、搜索、详情、详情的文件清单、下载、健康检测、登录、用户数据、原站视图导航)记一次请求;挂站点的 Feed 每次真正发出 RSS 请求(不含熔断 / 退避跳过)也记一次(HH-2 之前漏算)。写 `site_request_count`(迁移 `0046`,按小时聚合,多进程共用,重启不丢,保留 7 天)。`GET /sites/{id}/request-stats`(read)返回最近 24 小时总数、按小时明细、快照实际周期与合并后的访问默认;站点页概览「近 24 小时请求」与资源页列表头显示。计数从迁移 0046 上线后开始累计,之前的请求没有记录,无法回填;RSS 请求从 HH-2 起计入。 ## 详情页扩展内容(INT-2) @@ -99,6 +103,8 @@ - 缓存:内容放 PG `kv_cache` region `site.detail`,key = `source_release.id`,TTL 取站点详情缓存期(至少 24 小时)。缓存期内 `detail_fetched_at` 新鲜且缓存还在就不请求站点;kv_cache 被清掉(过期 / 重启丢 UNLOGGED 表)后下次刷新会重新抓一次。 - 外部 ID 另写 `source_release.raw._detail`(`imdbId / doubanId / tmdb{type,id}`),列表快照 upsert 不覆盖它。身份解析把它和列表给的 `raw._site.imdbId / doubanId` 一起当显式 ID:本地召回查已接受的外部身份;需要查 TMDB 时,有 TMDB ID 直接取详情,否则 IMDb ID 走 `/find/{imdb}?external_source=imdb_id`,命中就不再按标题搜索;TMDB 不支持豆瓣 ID,豆瓣只用于本地召回。是否查 TMDB 仍按惰性识别规则(DEC-1),外部 ID 本身不触发查询。 - 截图:响应里只有代理路径 `/api/v1/img/site-detail/{releaseId}/{index}`,服务端从缓存取原始地址,公网校验(每跳重定向复核)、不带站点凭据、按文件头认图片(不放行 SVG)、≤ 8 MB,浏览器缓存 24 小时。站内附件如果需要登录才能看会显示为裂图,不会为截图消耗站点请求。 +- 憨憨详情页(HH-2 按真实页面 `probe.labels` 核对):没有 `#kdescr`,内容分节为「标签 div + 相邻值 div」:`mediainfo`、`海报`、`豆瓣`(内有 `span.font-bold` 小标签 片名 / 年代 / 类别 / 导演 …)、`截图`、`种子文件`(只有「查看列表」按钮)、`发布者`、`发布时间`。对应规则:简介没有容器时取「豆瓣 / 影片信息 / IMDb」信息块,小标签补冒号、一项一行;截图取「截图」一节的 img(`data-src` 优先,海报不算截图),没有再取简介里的图;IMDb 依次取页面链接 → 简介 / 信息块 / MediaInfo 里的 `imdb.com/title/tt…` 或 `IMDB : tt…` 行(只收 `tt` + 7~8 位,不收孤立 tt 串),TMDB 取链接或 `TMDB : tv/294990` 行,豆瓣取链接或 `douban.com/subject/…`;兜底出的 IMDb / 豆瓣也回填到 `SiteRelease.imdbId / doubanId`;发布时间没有 `span[title]` 时取「发布时间 / 上传时间」字段文本。文件清单:定义 `umt.detail.page.fileListPath`(憨憨 `viewfilelist.php?id={id}`)配置了且页面上没有内联清单时,同一次详情刷新里再请求 1 次(走同一限流器、计数),失败只是没有清单。fixture `HHANCLUB_DETAIL_PAGE_2026` / `HHANCLUB_FILELIST_FRAGMENT`:标签名称与顺序来自真实页面,嵌套按已验证的标题 / 副标题结构推定,部署后打开一条憨憨资源的详情抽屉核对一次。 +- 截图代理的 Referer:截图与站点同根域(憨憨图床 `pic.hhanclub.net`、`proxy-cover.hhanclub.net`)时带 `Referer: <站点地址>/`(防盗链),外部图床(如 pixhost)不带;任何情况都不带 Cookie。 - 解析:`packages/adapters/src/sites/spider/detail-page.ts`。简介取 MovieClaw `detail_description_css`(缺省 `#kdescr`);MediaInfo 取定义 `umt.detail.page.mediaInfo` → 「MediaInfo」字段标签 → 简介里第一个像 MediaInfo 的引用 / 代码块;文件清单取 `umt.detail.page.files` → MovieClaw `detail_file_list_css` → 「文件列表」字段 → `#filelist` 表格,不额外请求 `viewfilelist.php`。字段标签除 `td.rowhead` 外可在 `umt.detail.page.labelSelectors` 加(憨憨:`div.font-bold.leading-6`、`span.font-bold`)。`page.probe` 记录页面上找到的字段标签,只存缓存不展示,站点改版时用来排查。馒头从 API 的 `descr`(BBCode)与 `mediainfo` 取同样的结构。 ## Agent 能力(W10-pre2) diff --git a/packages/adapters/src/sites/definitions/hhanclub.json b/packages/adapters/src/sites/definitions/hhanclub.json index 303500e9..4c46f915 100644 --- a/packages/adapters/src/sites/definitions/hhanclub.json +++ b/packages/adapters/src/sites/definitions/hhanclub.json @@ -260,7 +260,8 @@ "fileList": "td.rowfollow > ul > li::text" }, "page": { - "labelSelectors": ["div.font-bold.leading-6", "span.font-bold"] + "labelSelectors": ["div.font-bold.leading-6", "span.font-bold"], + "fileListPath": "viewfilelist.php?id={id}" } }, "vipLevels": ["贵宾", "VIP"], diff --git a/packages/adapters/src/sites/definitions/schema.ts b/packages/adapters/src/sites/definitions/schema.ts index 081fe19b..83fe8ae2 100644 --- a/packages/adapters/src/sites/definitions/schema.ts +++ b/packages/adapters/src/sites/definitions/schema.ts @@ -149,6 +149,7 @@ const UmtNexusSchema = z.strictObject({ tags: z.string().optional(), uploader: z.string().optional(), files: z.string().optional(), + fileListPath: z.string().optional(), }) .optional(), }) diff --git a/packages/adapters/src/sites/detail-text.ts b/packages/adapters/src/sites/detail-text.ts index d7318537..ea569a30 100644 --- a/packages/adapters/src/sites/detail-text.ts +++ b/packages/adapters/src/sites/detail-text.ts @@ -18,9 +18,12 @@ export function looksLikeMediaInfo(text: string): boolean { return (text.match(/^[^\n::]{2,40}[ \t]*[::][ \t]*\S/gm) ?? []).length >= 5; } +/** TMDB:themoviedb.org 链接,或 MediaInfo / 简介里的「TMDB : tv/294990」一行 */ export function extractTmdb(text: string | null | undefined): SiteDetailPage["tmdb"] { - const m = text ? /themoviedb\.org\/(movie|tv)\/(\d+)/.exec(text) : null; - return m ? { type: m[1] as "movie" | "tv", id: m[2]! } : null; + const m = text + ? (/themoviedb\.org\/(movie|tv)\/(\d+)/.exec(text) ?? /\bTMDB\s*(?:ID)?\s*[::]\s*(movie|tv)\/(\d+)/i.exec(text)) + : null; + return m ? { type: m[1]!.toLowerCase() as "movie" | "tv", id: m[2]! } : null; } /** 图片地址转绝对 http(s) 地址并过滤非截图;无效返回 null */ diff --git a/packages/adapters/src/sites/feed-item.test.ts b/packages/adapters/src/sites/feed-item.test.ts new file mode 100644 index 00000000..29e1f072 --- /dev/null +++ b/packages/adapters/src/sites/feed-item.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import { normalizeSiteFeedItem } from "./feed-item.ts"; + +// 标题取自 2026-09-29 憨憨 RSS 真实条目(公开种子名,无凭据) +describe("站点 RSS 条目规范化(憨憨)", () => { + it("[分类]种子名[副标题][大小] → 分类 / 标题 / 副标题 / 大小", () => { + const r = normalizeSiteFeedItem( + "hhanclub", + "[音乐]Travis - L.A. Times (2024) - WEB-DL - 24bit ALAC-HHWEB[Travis - L.A. Times | ALAC | 苹果音乐 | 现场版专辑][697.64 MB]", + ); + expect(r).toEqual({ + title: "Travis - L.A. Times (2024) - WEB-DL - 24bit ALAC-HHWEB", + subtitle: "Travis - L.A. Times | ALAC | 苹果音乐 | 现场版专辑", + category: { id: "410", name: "音乐" }, + mediaType: "music", + sizeBytes: Math.round(697.64 * 1024 ** 2), + uploader: null, + }); + }); + + it("电视剧:副标题里的 | 与 / 保留,GB 换算", () => { + const r = normalizeSiteFeedItem( + "hhanclub", + "[电视剧]Spring of the Blade S01 2026 2160p YK WEB-DL H.265 DV HFR HQ AAC2.0-HHWEB[一瓯春 | 第20-24集 | 4K 高码 杜比视界 | 类型: 爱情/古装][26.37 GB]", + ); + expect(r.title).toBe("Spring of the Blade S01 2026 2160p YK WEB-DL H.265 DV HFR HQ AAC2.0-HHWEB"); + expect(r.subtitle).toBe("一瓯春 | 第20-24集 | 4K 高码 杜比视界 | 类型: 爱情/古装"); + expect(r.category).toEqual({ id: "402", name: "电视剧" }); + expect(r.mediaType).toBe("tv"); + expect(r.sizeBytes).toBe(Math.round(26.37 * 1024 ** 3)); + }); + + it("副标题内嵌方括号、[新] 标记、不在任何媒体大类的分类(纪录片)", () => { + const r = normalizeSiteFeedItem( + "hhanclub", + "[纪录片]Thirteen Talks S09 2016 2160p WEB-DL H.265 AAC2.0-HHWEB[新][十三邀 | 第14集 [内嵌中字]][3.6 GB]", + ); + expect(r.title).toBe("Thirteen Talks S09 2016 2160p WEB-DL H.265 AAC2.0-HHWEB"); + expect(r.subtitle).toBe("十三邀 | 第14集 [内嵌中字]"); + expect(r.category).toEqual({ id: "404", name: "纪录片" }); + expect(r.mediaType).toBeNull(); + }); + + it("带上传者段:[副标题][大小][上传者]", () => { + const r = normalizeSiteFeedItem("hhanclub", "[电影]Some Movie 2024 1080p WEB-DL-HHWEB[某电影][8.5 GB][uploader01]"); + expect(r).toMatchObject({ + title: "Some Movie 2024 1080p WEB-DL-HHWEB", + subtitle: "某电影", + uploader: "uploader01", + }); + }); + + it("没有大小段时不拆尾部方括号;未登记的前缀不当分类", () => { + const r = normalizeSiteFeedItem("hhanclub", "[BDMV]Some.Disc.2024[Extras]"); + expect(r).toMatchObject({ title: "[BDMV]Some.Disc.2024[Extras]", subtitle: null, sizeBytes: null }); + expect(r.category).toEqual({ id: null, name: null }); + }); + + it("未知站点只去 [新]", () => { + const r = normalizeSiteFeedItem(null, "[电影]Foo 2024[新][foo][1 GB]"); + expect(r.category.id).toBeNull(); + expect(r.title).toBe("[电影]Foo 2024"); + expect(r.subtitle).toBe("foo"); + }); +}); diff --git a/packages/adapters/src/sites/feed-item.ts b/packages/adapters/src/sites/feed-item.ts new file mode 100644 index 00000000..d184628f --- /dev/null +++ b/packages/adapters/src/sites/feed-item.ts @@ -0,0 +1,123 @@ +/** + * 站点 RSS 条目规范化 —— 本项目扩展(站点适配层)。 + * NexusPHP `torrentrss.php` 按用户勾选拼标题:`[分类]` + 种子名 + `[副标题]` + `[大小]` + `[上传者]` + * (憨憨:`[音乐]Travis - L.A. Times (2024) - WEB-DL - 24bit ALAC-HHWEB[Travis - L.A. Times | ALAC][697.64 MB]`)。 + * 这里把它拆成与列表快照一致的字段:干净标题、副标题、站点分类(ID + 名称 + 媒体大类)、大小。 + * 只认站点定义里登记过的分类名作前缀;不改标题解析器(release-parser)。 + */ +import { getSiteDefinition } from "./registry.ts"; +import { parseSizeMp } from "./spider/spider.ts"; + +export type FeedMediaType = "movie" | "tv" | "music"; + +export interface SiteFeedItemFields { + /** 去掉分类前缀、副标题、大小、上传者、「[新]」后的种子名 */ + title: string; + subtitle: string | null; + /** 站点原始分类:ID 取站点定义里同名分类(与列表快照的 category_raw 一致);没有登记为 null */ + category: { id: string | null; name: string | null }; + /** 站点分类所属的媒体大类(定义 category.movie / tv / music);不属于任何一组为 null */ + mediaType: FeedMediaType | null; + /** 标题尾部的大小(站点四舍五入后的显示值);enclosure 有 length 时调用方应优先用它 */ + sizeBytes: number | null; + uploader: string | null; +} + +const NEW_MARK = /\s*[[((【]\s*新\s*[\]))】]\s*/g; +const SIZE = /^\s*([\d.,]+\s*[KMGTP]i?B)\s*$/i; + +interface CategoryDefLike { + category?: Partial>; + umt?: { categoryNames?: Record }; +} + +/** 站点定义里登记的「分类名 → ID」 */ +function categoryIndex(siteKey: string | null | undefined): Map { + const out = new Map(); + const def = (siteKey ? getSiteDefinition(siteKey) : undefined) as CategoryDefLike | undefined; + if (!def) return out; + for (const [id, name] of Object.entries(def.umt?.categoryNames ?? {})) if (name) out.set(name, id); + for (const k of ["movie", "tv", "music"] as const) { + for (const c of def.category?.[k] ?? []) { + if (typeof c.desc === "string" && c.desc && c.id != null && !out.has(c.desc)) out.set(c.desc, String(c.id)); + } + } + return out; +} + +/** 站点分类 ID → 媒体大类(只在唯一一组里出现时成立) */ +export function siteCategoryMediaType(siteKey: string | null | undefined, id: string | null): FeedMediaType | null { + if (!id || !siteKey) return null; + const def = getSiteDefinition(siteKey) as CategoryDefLike | undefined; + const hits = (["movie", "tv", "music"] as const).filter((k) => + (def?.category?.[k] ?? []).some((c) => c.id != null && String(c.id) === id), + ); + return hits.length === 1 ? hits[0]! : null; +} + +/** 从尾部剥一个配平的 `[...]`;不以 `]` 结尾或不配平返回 null */ +function popTrailingGroup(s: string): { rest: string; inner: string } | null { + const t = s.trimEnd(); + if (!t.endsWith("]")) return null; + let depth = 0; + for (let i = t.length - 1; i >= 0; i--) { + const ch = t[i]; + if (ch === "]") depth++; + else if (ch === "[") { + depth--; + if (depth === 0) return { rest: t.slice(0, i), inner: t.slice(i + 1, -1) }; + } + } + return null; +} + +/** + * 规范化站点 RSS 条目标题。没有站点定义时只去「[新]」,其余原样返回。 + * 只有标题尾部出现大小段(`[697.64 MB]`)时才把它前面的一段当副标题,避免误伤本身以 `[...]` 结尾的种子名。 + */ +export function normalizeSiteFeedItem(siteKey: string | null | undefined, rawTitle: string): SiteFeedItemFields { + const cats = categoryIndex(siteKey); + let s = rawTitle.replace(/\s+/g, " ").trim(); + let category: SiteFeedItemFields["category"] = { id: null, name: null }; + + const prefix = /^\s*\[([^\][]{1,16})\]\s*/.exec(s); + if (prefix && cats.has(prefix[1]!.trim())) { + const name = prefix[1]!.trim(); + category = { id: cats.get(name) ?? null, name }; + s = s.slice(prefix[0].length); + } + + let sizeBytes: number | null = null; + let subtitle: string | null = null; + let uploader: string | null = null; + const groups: string[] = []; + let rest = s; + // 最多看尾部 3 段:[副标题][大小][上传者] + for (let i = 0; i < 3; i++) { + const g = popTrailingGroup(rest); + if (!g) break; + groups.unshift(g.inner); + rest = g.rest; + } + const sizeIdx = groups.findIndex((g) => SIZE.test(g)); + if (sizeIdx >= 0) { + const bytes = parseSizeMp(SIZE.exec(groups[sizeIdx]!)![1]!); + sizeBytes = bytes > 0 ? bytes : null; + const before = groups.slice(0, sizeIdx); + const after = groups.slice(sizeIdx + 1); + // 大小之前最后一段是副标题,再往前的属于种子名本身 + if (before.length) subtitle = before.pop()!.trim() || null; + if (after.length === 1 && after[0]!.trim().length <= 32) uploader = after[0]!.trim() || null; + s = `${rest}${before.map((b) => `[${b}]`).join("")}${after.length > 1 ? after.map((a) => `[${a}]`).join("") : ""}`; + } + + const title = s.replace(NEW_MARK, " ").replace(/\s+/g, " ").trim() || rawTitle.trim(); + return { + title, + subtitle: subtitle ? subtitle.replace(NEW_MARK, " ").trim() || null : null, + category, + mediaType: siteCategoryMediaType(siteKey, category.id), + sizeBytes, + uploader, + }; +} diff --git a/packages/adapters/src/sites/index.ts b/packages/adapters/src/sites/index.ts index 64e13ddc..03be54dc 100644 --- a/packages/adapters/src/sites/index.ts +++ b/packages/adapters/src/sites/index.ts @@ -16,6 +16,12 @@ export { SpiderFilterSchema, } from "./definitions/index.ts"; export type { SiteAdapterDeps } from "./deps.ts"; +export { + type FeedMediaType, + normalizeSiteFeedItem, + type SiteFeedItemFields, + siteCategoryMediaType, +} from "./feed-item.ts"; export { classifyLoginError, loginFailureText, diff --git a/packages/adapters/src/sites/spider/adapter.ts b/packages/adapters/src/sites/spider/adapter.ts index 32116d8e..8d7de10a 100644 --- a/packages/adapters/src/sites/spider/adapter.ts +++ b/packages/adapters/src/sites/spider/adapter.ts @@ -26,6 +26,7 @@ import { } from "../types.ts"; import { inspectTorrent } from "../util.ts"; import { parseDetailPage } from "./detail.ts"; +import { filesFromFileListHtml } from "./detail-page.ts"; import { spiderTorrentToRelease } from "./map.ts"; import type { IndexerDefinition } from "./schema.ts"; import { loadHtml, normalizeSelector, SiteSpider } from "./spider.ts"; @@ -178,6 +179,17 @@ export function createSpiderSiteAdapter(input: IndexerDefinition, deps: SiteAdap if (r.kind === "login") throw new SiteError(def.id, "auth_expired", `详情 ${siteReleaseId}:返回登录页`); if (r.kind === "not_found") throw new SiteError(def.id, "not_found", `详情 ${siteReleaseId}:资源不存在`); if (!r.detail) throw new SiteError(def.id, "parse_error", `详情 ${siteReleaseId}:页面结构无法识别`); + // 文件清单不在页面上(NexusPHP 点「查看列表」才 AJAX 取)→ 按需 1 次;失败只是没有文件清单 + const flPath = ext.detail.page?.fileListPath; + if (flPath && r.detail.page && !r.detail.page.files.length) { + try { + const flUrl = new URL(flPath.replace("{id}", siteReleaseId), base).toString(); + const frag = await fetchPage(flUrl, `文件清单 ${siteReleaseId}`, opts); + r.detail.page.files = filesFromFileListHtml(loadHtml(frag)); + } catch (e) { + if (e instanceof SiteError && e.isAuthError) throw e; + } + } return r.detail; }, diff --git a/packages/adapters/src/sites/spider/detail-page.test.ts b/packages/adapters/src/sites/spider/detail-page.test.ts index 257afa5d..26a78b52 100644 --- a/packages/adapters/src/sites/spider/detail-page.test.ts +++ b/packages/adapters/src/sites/spider/detail-page.test.ts @@ -1,8 +1,11 @@ -import { HHANCLUB_DETAIL_PAGE_FULL } from "@umt/testkit"; +import { HHANCLUB_DETAIL_PAGE_2026, HHANCLUB_DETAIL_PAGE_FULL, HHANCLUB_FILELIST_FRAGMENT } from "@umt/testkit"; import { describe, expect, it } from "vitest"; import { hhanclubDefinition } from "../definitions/index.ts"; import { looksLikeMediaInfo, pageFromBbcode } from "../detail-text.ts"; +import { createSiteRateLimiter } from "../rate-limit.ts"; import { getSiteDefinition } from "../registry.ts"; +import { staticSecretResolver } from "../types.ts"; +import { createSpiderSiteAdapter } from "./adapter.ts"; import { parseDetailPage } from "./detail.ts"; import type { IndexerDefinition } from "./schema.ts"; @@ -105,3 +108,58 @@ Format : AVC[/quote] expect(looksLikeMediaInfo("General\n一段文字\nVideo")).toBe(false); }); }); + +describe("详情页扩展内容(憨憨 2026 真实结构:分节 div,无 #kdescr)", () => { + const { kind, detail } = parseDetailPage(HHANCLUB_DETAIL_PAGE_2026, hhanclubDefinition, "91003", OBSERVED); + const page = detail!.page!; + + it("MediaInfo 取「mediainfo」一节;IMDb / TMDB 从 MediaInfo 行兜底,豆瓣取页面链接", () => { + expect(kind).toBe("ok"); + expect(page.mediaInfo).toContain("Complete name"); + expect(page).toMatchObject({ imdbId: "tt45950001", tmdb: { type: "tv", id: "290001" }, doubanId: "36000002" }); + expect(detail!.imdbId).toBe("tt45950001"); + }); + + it("简介取豆瓣信息块,小标签补冒号、一项一行", () => { + expect(page.descriptionText).toContain("片名:示例剧集"); + expect(page.descriptionText).toContain("类别:爱情 / 古装"); + expect(page.descriptionText).toContain("一段剧情简介"); + }); + + it("截图取「截图」一节(优先 data-src),不含海报与占位图", () => { + expect(page.screenshots).toEqual([ + "https://pic.hhanclub.net/img/2026/09/29/shot1.png", + "https://img3.pixhost.cc/images/1/shot2.jpg", + ]); + }); + + it("上传者、标签、发布时间(字段文本兜底)", () => { + expect(page.uploader).toBe("匿名"); + expect(page.tags).toEqual(["官方", "国语", "中字", "杜比视界"]); + expect(detail!.uploadedAt).toBe("2026-09-29T08:42:57.000Z"); + expect(page.files).toEqual([]); + }); + + it("文件清单不在页面上 → 按需请求 viewfilelist.php 一次", async () => { + const paths: string[] = []; + const fetch = (async (input: RequestInfo | URL) => { + const u = new URL(String(input)); + paths.push(`${u.pathname}${u.search}`); + const body = u.pathname === "/viewfilelist.php" ? HHANCLUB_FILELIST_FRAGMENT : HHANCLUB_DETAIL_PAGE_2026; + return new Response(body, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } }); + }) as typeof globalThis.fetch; + const limiter = createSiteRateLimiter({ minIntervalMs: 0 }); + const a = createSpiderSiteAdapter(hhanclubDefinition, { + fetch, + limiter, + secrets: staticSecretResolver({ hhanclub: { cookie: "c=1" } }), + }); + const d = await a.detail("91003"); + expect(paths).toEqual(["/details.php?id=91003&hit=1", "/viewfilelist.php?id=91003"]); + expect(limiter.stats.granted).toBe(2); + expect(d.page!.files).toEqual([ + { name: "Example.Show.S01E18.2026.2160p.mkv", sizeBytes: Math.round(5.04 * 1024 ** 3) }, + { name: "Example.Show.S01E19.2026.2160p.mkv", sizeBytes: Math.round(4.98 * 1024 ** 3) }, + ]); + }); +}); diff --git a/packages/adapters/src/sites/spider/detail-page.ts b/packages/adapters/src/sites/spider/detail-page.ts index dfa14487..2f2cca81 100644 --- a/packages/adapters/src/sites/spider/detail-page.ts +++ b/packages/adapters/src/sites/spider/detail-page.ts @@ -25,10 +25,13 @@ type El = ReturnType; const LABELS = { description: ["简介", "Description"], + /** 没有简介容器时的次选:豆瓣 / IMDb 信息块(憨憨把豆瓣资料拆成独立一节) */ + descriptionFallback: ["豆瓣", "豆瓣信息", "影片信息", "IMDb信息", "IMDb"], + screenshots: ["截图", "Screenshots", "Screenshot"], mediaInfo: ["MediaInfo", "媒体信息", "BDInfo"], tags: ["标签", "Tags"], uploader: ["上传者", "发布者", "发布人", "上传人", "Uploader"], - files: ["文件列表", "文件清单", "File list", "Files"], + files: ["文件列表", "文件清单", "种子文件", "File list", "Files"], }; /** 简介里可能装 MediaInfo 的块 */ @@ -62,6 +65,19 @@ function fileFromText(text: string): SiteDetailFile | null { return HAS_EXT.test(t) ? { name: t, sizeBytes: null } : null; } +/** 文本里的 IMDb ID:imdb.com/title 链接或「IMDB : tt…」一行;不接受孤立的 tt 串(避免误中哈希) */ +export function imdbFromText(text: string): string | null { + const m = + /imdb\.com\/title\/(tt\d{7,8})\b/i.exec(text) ?? + /\bIMDb\s*(?:ID|链接|編號|编号)?\s*[::]?\s*(tt\d{7,8})\b/i.exec(text); + return m?.[1] ?? null; +} + +/** NexusPHP viewfilelist.php 返回的文件表(按需 AJAX 取到的 HTML 片段) */ +export function filesFromFileListHtml($: CheerioAPI): SiteDetailFile[] { + return filesFrom($, $("tr")); +} + function filesFrom($: CheerioAPI, rows: El): SiteDetailFile[] { const out: SiteDetailFile[] = []; rows.each((_, n) => { @@ -70,7 +86,7 @@ function filesFrom($: CheerioAPI, rows: El): SiteDetailFile[] { const cells = row.children("td"); if (cells.length >= 2) { const name = cells.first().text().replace(/\s+/g, " ").trim(); - const size = cells.eq(1).text().trim(); + const size = cells.eq(1).text().replace(/\s+/g, " ").trim(); if (name && HAS_EXT.test(name) && /\d/.test(size)) out.push({ name, sizeBytes: parseSizeMp(size) || null }); return; } @@ -106,6 +122,7 @@ export function extractDetailPage($: CheerioAPI, conf: DetailConf, base: string) let desc: El | null = descSel ? $(descSel).first() : null; if (!desc?.length) desc = byLabel(LABELS.description); page.probe.hasDescription = !!desc?.length; + const infoBlock = desc?.length ? null : byLabel(LABELS.descriptionFallback); // MediaInfo:站点选择器 → 字段标签 → 简介里的引用 / 代码块(取最内层文本,整块从简介里去掉) let mediaInfoEl: El | null = null; @@ -137,12 +154,19 @@ export function extractDetailPage($: CheerioAPI, conf: DetailConf, base: string) // 截图 const imgs: string[] = []; - const imgEls = p.screenshots ? $(p.screenshots) : desc?.length ? desc.find("img") : null; - imgEls?.each((_, n) => { - const e = $(n); - const u = screenshotUrl(e.attr("data-src") ?? e.attr("data-original") ?? e.attr("src"), base); - if (u && !imgs.includes(u)) imgs.push(u); - }); + const pushImgs = (els: El | null) => + els?.each((_, n) => { + const e = $(n); + const u = screenshotUrl(e.attr("data-src") ?? e.attr("data-original") ?? e.attr("src"), base); + if (u && !imgs.includes(u)) imgs.push(u); + }); + if (p.screenshots) pushImgs($(p.screenshots)); + else { + // 独立的「截图」一节(憨憨)优先;没有再取简介里的图 + const shotBlock = byLabel(LABELS.screenshots); + if (shotBlock) pushImgs(shotBlock.is("img") ? shotBlock : shotBlock.find("img")); + if (!imgs.length && desc?.length) pushImgs(desc.find("img")); + } page.probe.imageCount = imgs.length; page.screenshots = imgs.slice(0, MAX_SCREENSHOTS); @@ -153,18 +177,36 @@ export function extractDetailPage($: CheerioAPI, conf: DetailConf, base: string) c.find("img").remove(); const t = blockText($, c); if (t) page.descriptionText = t.slice(0, 20_000); + } else if (infoBlock) { + const c = infoBlock.clone(); + c.find("img").remove(); + // 块里的「标签」span 与值之间补上冒号 / 换行,读起来是「片名:xxx」一行一项 + c.find(p.labelSelectors?.join(", ") || "span.font-bold").each((_, n) => { + const e = $(n); + const t = e.text().trim(); + if (t && t.length <= 16 && !/[::]$/.test(t)) e.text(`${t}:`); + e.before("\n"); + }); + const t = blockText($, c) + .replace(/[ \t]*\n[ \t]*/g, "\n") + .replace(/:\s*\n(?=\S)/g, ":"); + if (t) page.descriptionText = t.slice(0, 20_000); } - // 外部 ID:页面上的链接优先,其次简介正文 - const descHtml = desc?.html() ?? ""; + // 外部 ID:页面上的链接优先,其次简介 / 信息块正文,再次 MediaInfo 里的 IMDB / TMDB 行(憨憨的 MediaInfo 带这两行) + const descHtml = `${desc?.html() ?? ""}\n${infoBlock?.html() ?? ""}`; + const idText = `${descHtml}\n${page.mediaInfo ?? ""}`; const f = conf.fields ?? {}; page.imdbId = - extractImdbId(mcSelect($, null, f.imdb ?? "a[href*='imdb.com']::attr(href)")) ?? extractImdbId(descHtml) ?? null; + extractImdbId(mcSelect($, null, f.imdb ?? "a[href*='imdb.com']::attr(href)")) ?? + imdbFromText(idText) ?? + extractImdbId(descHtml) ?? + null; page.doubanId = extractDoubanId(mcSelect($, null, f.douban ?? "a[href*='douban.com']::attr(href)")) ?? - extractDoubanId(descHtml) ?? + extractDoubanId(idText) ?? null; - page.tmdb = extractTmdb($("a[href*='themoviedb.org']").first().attr("href")) ?? extractTmdb(descHtml); + page.tmdb = extractTmdb($("a[href*='themoviedb.org']").first().attr("href")) ?? extractTmdb(idText); // 文件清单:站点选择器 → MovieClaw file_list(去伪元素)→ 字段标签 → #filelist 表格 const fileSel = p.files ?? f.fileList?.replace(/::text$/, ""); diff --git a/packages/adapters/src/sites/spider/detail.ts b/packages/adapters/src/sites/spider/detail.ts index 4613e760..ce417a5c 100644 --- a/packages/adapters/src/sites/spider/detail.ts +++ b/packages/adapters/src/sites/spider/detail.ts @@ -83,10 +83,15 @@ export function parseDetailPage( browse: undefined, }; const promo = new SiteSpider(promoDef).infoFrom($, $(conf.headerScope).first()); - const timeTitle = $("span[title]") - .filter((_, el) => /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test($(el).attr("title") ?? "")) - .first() - .attr("title"); + // 发布时间:带完整时间的 span[title] → 「发布时间 / 上传时间 / 添加时间」字段的文本(憨憨是 div 标签 + 值) + const timeTitle = + $("span[title]") + .filter((_, el) => /\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test($(el).attr("title") ?? "")) + .first() + .attr("title") ?? + /(?:发布时间|上传时间|添加时间|Added)\s*[::]?\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}(?::\d{2})?)/.exec( + $("body").text().replace(/\s+/g, " "), + )?.[1]; const detail: SiteReleaseDetail = { siteId: def.id, @@ -119,6 +124,9 @@ export function parseDetailPage( const page = extractDetailPage($, conf, def.domain); page.imdbId ??= detail.imdbId ?? null; page.doubanId ??= detail.doubanId ?? null; + // 详情页扩展内容里兜底出来的外部 ID(MediaInfo 的 IMDB 行、豆瓣信息块)也回填到资源本身 + if (!detail.imdbId && page.imdbId) detail.imdbId = page.imdbId; + if (!detail.doubanId && page.doubanId) detail.doubanId = page.doubanId; if (page.tags.length) detail.tags = page.tags; detail.page = page; return { kind: "ok", detail }; diff --git a/packages/adapters/src/sites/spider/schema.ts b/packages/adapters/src/sites/spider/schema.ts index bbc13714..4a7d00b8 100644 --- a/packages/adapters/src/sites/spider/schema.ts +++ b/packages/adapters/src/sites/spider/schema.ts @@ -211,6 +211,11 @@ export interface UmtSiteExtension { uploader?: string; /** 文件清单行(每行一个文件,文本形如「名称 (大小)」或两列表格) */ files?: string; + /** + * 页面上没有内联文件清单时,按需再取一次的 AJAX 端点(`{id}` 替换为资源 ID,NexusPHP 为 `viewfilelist.php?id={id}`); + * 每次详情刷新最多 1 次,失败不影响详情。 + */ + fileListPath?: string; }; }; /** 下载路径(按资源 ID 拼接) */ diff --git a/packages/contracts/src/browser.ts b/packages/contracts/src/browser.ts index 96b9bc14..4e232de0 100644 --- a/packages/contracts/src/browser.ts +++ b/packages/contracts/src/browser.ts @@ -356,6 +356,8 @@ export const BrowserSessionView = BrowserSession.extend({ needsReconnect: z.boolean().optional(), /** 最近一次自动重连的结果 */ reconnect: BrowserReconnectState.nullable().optional(), + /** 最近一次自动化(Agent 能力)动作成功的时间;从未有过为 null。原站视图据此显示「Agent」或「自动化」 */ + lastAutomationAt: Timestamp.nullable().optional(), }); export type BrowserSessionView = z.infer; diff --git a/packages/testkit/src/sites/fixtures/definitions/hhanclub.ts b/packages/testkit/src/sites/fixtures/definitions/hhanclub.ts index e6f32a6e..a4812cb7 100644 --- a/packages/testkit/src/sites/fixtures/definitions/hhanclub.ts +++ b/packages/testkit/src/sites/fixtures/definitions/hhanclub.ts @@ -108,3 +108,49 @@ export const HHANCLUB_DETAIL_PAGE_FULL = `HHCL <div class="font-bold leading-6">文件列表</div> <div class="font-bold leading-6"><ul><li>Example.Show.S01E01.mkv (1.04 GB)</li><li>Example.Show.S01E02.mkv (1.02 GB)</li></ul></div> </div></body></html>`; + +/** + * HH-2:按 2026-09-29 真实详情页的解析诊断(`page.probe.labels`)重建的最小片段。字段标签名称与顺序来自真实页面 + * (下载 / 标题 / 副标题 / 标签 / 基本信息 / 字幕 / mediainfo / 海报 / 豆瓣 / 截图 / 种子文件 / 同伴 …,豆瓣块里是 + * span.font-bold 小标签 片名 / 年代 / 类别 / 导演 …);没有 #kdescr 简介容器;MediaInfo 头部与 IMDB / TMDB 两行 + * 摘自真实缓存(ID 已替换)。标签与值的 DOM 嵌套是按标题 / 副标题已验证的「标签 div + 相邻值 div」推定的。 + * 文件清单不在页面上,点「查看列表」才 AJAX 取 viewfilelist.php(见 HHANCLUB_FILELIST_FRAGMENT)。 + */ +export const HHANCLUB_DETAIL_PAGE_2026 = `<!DOCTYPE html><html><head><title>HHCLUB :: 种子详情 + +
+
下载
+
标题
Example Show S01 2026 2160p YK WEB-DL H.265 DV HQ AAC2.0-HHWEB
+
副标题
示例剧集 | 第18-24集 | 4K 高码 杜比视界
+
标签
官方国语中字杜比视界
+
基本信息
+
+ 大小:33.62 GB + 类型:电视剧 + 发布者:匿名 + 发布时间:2026-09-29 16:42:57 +
+
mediainfo
+
文件名:Example.Show.S01E18.2026.2160p.YK.WEB-DL.H.265.DV.HQ.AAC2.0-HHWEB.mkv
体 积: 5.04 GiB

General
Unique ID : 1580976599
Complete name : Example.Show.S01E18.mkv
Format : Matroska
File size : 5.04 GiB
Duration : 43 min 55 s
IMDB : tt45950001
TMDB : tv/290001

Video
ID : 1
Format : HEVC
+
海报
poster
+
豆瓣
+
+
片名示例剧集
+
年代2026
+
类别爱情 / 古装
+
导演某导演
+
一段剧情简介……
+ 豆瓣 +
+
截图
+
+
种子文件
+
同伴
22个做种者 | 66个下载者
+
`; + +/** NexusPHP viewfilelist.php 返回的文件表片段(标准结构,文件名 / 大小为示例) */ +export const HHANCLUB_FILELIST_FRAGMENT = ` + + + +
路径size
Example.Show.S01E18.2026.2160p.mkv5.04 GB
Example.Show.S01E19.2026.2160p.mkv4.98 GB
`; diff --git a/scripts/mp-sync/movieclaw-overrides/hhanclub.json b/scripts/mp-sync/movieclaw-overrides/hhanclub.json index d8994c9f..fde51b8b 100644 --- a/scripts/mp-sync/movieclaw-overrides/hhanclub.json +++ b/scripts/mp-sync/movieclaw-overrides/hhanclub.json @@ -29,7 +29,8 @@ }, "detail": { "page": { - "labelSelectors": ["div.font-bold.leading-6", "span.font-bold"] + "labelSelectors": ["div.font-bold.leading-6", "span.font-bold"], + "fileListPath": "viewfilelist.php?id={id}" } } },