opencode-deepseek-peak 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/tui.tsx", "../src/core/display.ts", "../src/i18n.ts", "../src/panel/FooterStatus.tsx", "../src/panel/PeakPanel.tsx", "../src/rpc.ts", "../src/v2/settings-menu.ts", "../src/v2/commands.ts"],
4
+ "sourcesContent": ["/** @jsxImportSource @opentui/solid */\n\nimport type { Plugin } from \"@opencode/plugin/tui\"\nimport { createSignal } from \"solid-js\"\nimport { INITIAL_SETTINGS, statusLabel } from \"./core/display.js\"\nimport { createT, detectLang, type LangCode } from \"./i18n.js\"\nimport { FooterStatus } from \"./panel/FooterStatus.js\"\nimport { PeakPanel } from \"./panel/PeakPanel.js\"\nimport { DeepSeekPeakRpc } from \"./rpc.js\"\nimport type { PeakStatus, TuiSettings } from \"./types.js\"\nimport { CommandRoot } from \"./v2/commands.js\"\n\nconst mod: Plugin.Definition = {\n id: \"opencode-deepseek-peak-tui\",\n setup(context) {\n const client = context.client.rpc(DeepSeekPeakRpc)\n const [settings, update] = context.storage.store<TuiSettings>(\n \"opencode-deepseek-peak.settings\",\n { initial: { ...INITIAL_SETTINGS, lang: detectLang() } },\n )\n // Stored settings may predate the `lang` field.\n const lang = () => (settings.lang ?? detectLang()) as LangCode\n const t = createT(lang)\n const [statuses, setStatuses] = createSignal<Record<string, PeakStatus>>({})\n const lastToast = new Map<string, string>()\n\n const unsubscribe = client.events.on(\"updated\", (event) => {\n const status = event.data as unknown as PeakStatus\n setStatuses((current) => ({ ...current, [status.sessionID]: status }))\n if (!settings.toast) return\n const fingerprint = `${status.period}:${status.mismatch}`\n if (!settings.toastEveryRequest && lastToast.get(status.sessionID) === fingerprint) return\n lastToast.set(status.sessionID, fingerprint)\n context.ui.toast.show({\n title: t(\"toast.title\"),\n message: statusLabel(status, t),\n variant: status.period === \"peak\" ? \"warning\" : \"success\",\n duration: 4500,\n })\n })\n\n const readStatus = async (sessionID: string) => {\n const result = await client.status({ sessionID }) as { found: boolean; status?: PeakStatus }\n if (result.found && result.status) return result.status\n try {\n const preview = await client.preview({ sessionID }) as { found: boolean; status?: PeakStatus }\n return preview.found ? preview.status : undefined\n } catch {\n return undefined\n }\n }\n\n // Pull the server's last observed status so the indicators render as soon\n // as a session opens, not only after the next DeepSeek request.\n const refresh = (sessionID: string) => {\n void readStatus(sessionID)\n .then((status) => {\n if (status) setStatuses((current) => ({ ...current, [status.sessionID]: status }))\n })\n .catch(() => {})\n }\n\n const unregisterCommands = context.ui.slot({\n append: \"app\",\n render: () => (\n <CommandRoot context={context} settings={settings} update={update} readStatus={readStatus} lang={lang} />\n ),\n })\n const unregisterSidebar = context.ui.slot({\n append: \"sidebar.content\",\n render: (props) => {\n const sessionID = String(props.sessionID ?? \"\")\n return (\n <PeakPanel\n context={context}\n status={() => statuses()[sessionID]}\n enabled={() => settings.sidebarIndicator !== false}\n evidence={() => settings.sidebarEvidence !== false}\n lang={lang}\n sessionID={sessionID}\n refresh={refresh}\n />\n )\n },\n })\n const unregisterFooter = context.ui.slot({\n append: \"prompt.footer.status\",\n render: (props) => {\n const sessionID = String(props.sessionID ?? \"\")\n return (\n <FooterStatus\n context={context}\n status={() => statuses()[sessionID]}\n enabled={() => settings.indicator}\n lang={lang}\n sessionID={sessionID}\n refresh={refresh}\n />\n )\n },\n })\n\n return () => {\n unsubscribe()\n unregisterCommands()\n unregisterSidebar()\n unregisterFooter()\n }\n },\n}\n\nexport default mod\n", "import type { Plugin } from \"@opencode/plugin/tui\"\nimport type { T, TranslationKey } from \"../i18n.js\"\nimport type { PeakStatus, TuiSettings } from \"../types.js\"\n\nexport const INITIAL_SETTINGS: TuiSettings = {\n toast: true,\n indicator: true,\n sidebarIndicator: true,\n sidebarEvidence: true,\n toastEveryRequest: false,\n lang: \"en\",\n}\n\nexport function statusLabel(status: PeakStatus, t: T) {\n const period = t(status.period === \"peak\" ? \"period.peak\" : \"period.offPeak\")\n const correction = status.mismatch ? ` \u00B7 API \u2260 ${t(\"evidence.schedule\")}` : \"\"\n return `DeepSeek ${period}${correction}`\n}\n\nconst HOLIDAY_KEYS: Record<string, TranslationKey> = {\n \"Mid-Autumn Festival\": \"holiday.midAutumn\",\n \"National Day\": \"holiday.nationalDay\",\n \"Spring Festival\": \"holiday.springFestival\",\n \"Qingming Festival\": \"holiday.qingming\",\n \"Dragon Boat Festival\": \"holiday.dragonBoat\",\n \"Labour Day\": \"holiday.labourDay\",\n}\n\n/** Short, sidebar-friendly, localized version of the status evidence. */\nexport function evidenceLabel(status: PeakStatus, t: T): string {\n let label: string\n // An API response header is the source of truth when present.\n if (status.source === \"api-header\") {\n label = t(\"evidence.apiHeader\", { header: status.evidence.split(\":\")[0] })\n } else if (status.holiday) {\n const key = HOLIDAY_KEYS[status.holiday]\n label = t(\"evidence.holiday\", { name: key ? t(key) : status.holiday })\n } else {\n label = t(\"evidence.schedule\")\n }\n if (status.evidence.endsWith(\"(cached)\")) label += ` ${t(\"evidence.cached\")}`\n if (status.evidence.includes(\"no request observed yet\")) label += ` ${t(\"evidence.preview\")}`\n return label\n}\n\n/**\n * Theme text tokens have two generations (base/muted and default/subdued), and\n * the feedback colors do too. Prefer the newer names and fall back, matching\n * the sibling plugins' mapping.\n */\nexport function themeColors(theme: Plugin.Context[\"theme\"]) {\n const text = theme.text\n const warning = text.feedback.warning\n return {\n normal: text.base ?? text.default,\n muted: text.muted ?? text.subdued,\n warning: warning.base ?? warning.default,\n }\n}\n", "// ---------------------------------------------------------------------------\n// i18n \u2014 centralized translations.\n// Add a language by appending a table that satisfies `Translation`; the\n// compiler enforces key completeness. Translation tables are product data and\n// stay localized; code comments are English.\n// ---------------------------------------------------------------------------\n\nexport type LangCode = \"en\" | \"pt\" | \"zh\" | \"ja\" | \"ko\"\n\nconst EN_T = {\n \"period.peak\": \"PEAK\",\n \"period.offPeak\": \"OFF-PEAK\",\n \"evidence.schedule\": \"official schedule\",\n \"evidence.apiHeader\": \"via {header}\",\n \"evidence.cached\": \"(cached)\",\n \"evidence.preview\": \"(no request yet)\",\n \"evidence.holiday\": \"Holiday: {name}\",\n \"label.mismatch\": \"mismatch\",\n \"toast.title\": \"DeepSeek pricing\",\n \"status.title\": \"DeepSeek Peak\",\n \"status.noSession\": \"Open a session first.\",\n \"status.noRequest\": \"No DeepSeek request observed in this session yet.\",\n \"status.source\": \"Source\",\n \"status.evidence\": \"Evidence\",\n \"status.unavailable\": \"DeepSeek Peak server plugin is unavailable.\",\n \"command.status.title\": \"DeepSeek Peak: Show Status\",\n \"command.status.description\": \"Show the last observed DeepSeek pricing period\",\n \"command.sections.title\": \"DeepSeek Peak: Sections\",\n \"command.sections.description\": \"Open the interactive sections menu (Esc to close)\",\n \"command.lang.title\": \"DeepSeek Peak: Language\",\n \"command.lang.description\": \"Switch display language\",\n \"settings.title\": \"DeepSeek Peak sections\",\n \"settings.toast\": \"Toast\",\n \"settings.footer\": \"Footer indicator\",\n \"settings.sidebar\": \"Sidebar indicator\",\n \"settings.evidence\": \"Sidebar evidence\",\n \"settings.frequency\": \"Toast frequency\",\n \"settings.lang\": \"Language\",\n \"settings.on\": \"on\",\n \"settings.off\": \"off\",\n \"frequency.change\": \"on change\",\n \"frequency.every\": \"every request\",\n \"holiday.midAutumn\": \"Mid-Autumn Festival\",\n \"holiday.nationalDay\": \"National Day\",\n \"holiday.springFestival\": \"Spring Festival\",\n \"holiday.qingming\": \"Qingming Festival\",\n \"holiday.dragonBoat\": \"Dragon Boat Festival\",\n \"holiday.labourDay\": \"Labour Day\",\n}\n\nexport type Translation = typeof EN_T\nexport type TranslationKey = keyof Translation\n\nconst PT_T: Translation = {\n \"period.peak\": \"PICO\",\n \"period.offPeak\": \"FORA DE PICO\",\n \"evidence.schedule\": \"hor\u00E1rio oficial\",\n \"evidence.apiHeader\": \"via {header}\",\n \"evidence.cached\": \"(cache)\",\n \"evidence.preview\": \"(sem requisi\u00E7\u00E3o ainda)\",\n \"evidence.holiday\": \"Feriado: {name}\",\n \"label.mismatch\": \"divergente\",\n \"toast.title\": \"Pre\u00E7o DeepSeek\",\n \"status.title\": \"DeepSeek Peak\",\n \"status.noSession\": \"Abra uma sess\u00E3o primeiro.\",\n \"status.noRequest\": \"Nenhuma requisi\u00E7\u00E3o DeepSeek observada nesta sess\u00E3o ainda.\",\n \"status.source\": \"Fonte\",\n \"status.evidence\": \"Evid\u00EAncia\",\n \"status.unavailable\": \"O plugin de servidor do DeepSeek Peak est\u00E1 indispon\u00EDvel.\",\n \"command.status.title\": \"DeepSeek Peak: Mostrar status\",\n \"command.status.description\": \"Mostra o \u00FAltimo per\u00EDodo de pre\u00E7o observado da DeepSeek\",\n \"command.sections.title\": \"DeepSeek Peak: Se\u00E7\u00F5es\",\n \"command.sections.description\": \"Abre o menu interativo de se\u00E7\u00F5es (Esc para fechar)\",\n \"command.lang.title\": \"DeepSeek Peak: Idioma\",\n \"command.lang.description\": \"Troca o idioma da interface\",\n \"settings.title\": \"Se\u00E7\u00F5es do DeepSeek Peak\",\n \"settings.toast\": \"Toast\",\n \"settings.footer\": \"Indicador no rodap\u00E9\",\n \"settings.sidebar\": \"Indicador na barra lateral\",\n \"settings.evidence\": \"Evid\u00EAncia na barra lateral\",\n \"settings.frequency\": \"Frequ\u00EAncia do toast\",\n \"settings.lang\": \"Idioma\",\n \"settings.on\": \"ativado\",\n \"settings.off\": \"desativado\",\n \"frequency.change\": \"ao mudar\",\n \"frequency.every\": \"a cada requisi\u00E7\u00E3o\",\n \"holiday.midAutumn\": \"Festival do Meio do Outono\",\n \"holiday.nationalDay\": \"Dia Nacional\",\n \"holiday.springFestival\": \"Festival da Primavera\",\n \"holiday.qingming\": \"Festival Qingming\",\n \"holiday.dragonBoat\": \"Festival do Barco Drag\u00E3o\",\n \"holiday.labourDay\": \"Dia do Trabalho\",\n}\n\nconst ZH_T: Translation = {\n \"period.peak\": \"\u9AD8\u5CF0\",\n \"period.offPeak\": \"\u4F4E\u8C37\",\n \"evidence.schedule\": \"\u5B98\u65B9\u65F6\u6BB5\",\n \"evidence.apiHeader\": \"\u6765\u81EA {header}\",\n \"evidence.cached\": \"\uFF08\u7F13\u5B58\uFF09\",\n \"evidence.preview\": \"\uFF08\u5C1A\u65E0\u8BF7\u6C42\uFF09\",\n \"evidence.holiday\": \"\u8282\u5047\u65E5\uFF1A{name}\",\n \"label.mismatch\": \"\u4E0D\u4E00\u81F4\",\n \"toast.title\": \"DeepSeek \u4EF7\u683C\",\n \"status.title\": \"DeepSeek Peak\",\n \"status.noSession\": \"\u8BF7\u5148\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\u3002\",\n \"status.noRequest\": \"\u6B64\u4F1A\u8BDD\u5C1A\u672A\u89C2\u5BDF\u5230 DeepSeek \u8BF7\u6C42\u3002\",\n \"status.source\": \"\u6765\u6E90\",\n \"status.evidence\": \"\u8BC1\u636E\",\n \"status.unavailable\": \"DeepSeek Peak \u670D\u52A1\u7AEF\u63D2\u4EF6\u4E0D\u53EF\u7528\u3002\",\n \"command.status.title\": \"DeepSeek Peak\uFF1A\u663E\u793A\u72B6\u6001\",\n \"command.status.description\": \"\u663E\u793A\u6700\u8FD1\u89C2\u5BDF\u5230\u7684 DeepSeek \u4EF7\u683C\u65F6\u6BB5\",\n \"command.sections.title\": \"DeepSeek Peak\uFF1A\u677F\u5757\",\n \"command.sections.description\": \"\u6253\u5F00\u4EA4\u4E92\u5F0F\u677F\u5757\u83DC\u5355\uFF08Esc \u5173\u95ED\uFF09\",\n \"command.lang.title\": \"DeepSeek Peak\uFF1A\u8BED\u8A00\",\n \"command.lang.description\": \"\u5207\u6362\u754C\u9762\u8BED\u8A00\",\n \"settings.title\": \"DeepSeek Peak \u677F\u5757\",\n \"settings.toast\": \"\u901A\u77E5\",\n \"settings.footer\": \"\u5E95\u90E8\u6307\u793A\u5668\",\n \"settings.sidebar\": \"\u4FA7\u8FB9\u680F\u6307\u793A\u5668\",\n \"settings.evidence\": \"\u4FA7\u8FB9\u680F\u8BC1\u636E\",\n \"settings.frequency\": \"\u901A\u77E5\u9891\u7387\",\n \"settings.lang\": \"\u8BED\u8A00\",\n \"settings.on\": \"\u5F00\",\n \"settings.off\": \"\u5173\",\n \"frequency.change\": \"\u53D8\u5316\u65F6\",\n \"frequency.every\": \"\u6BCF\u6B21\u8BF7\u6C42\",\n \"holiday.midAutumn\": \"\u4E2D\u79CB\u8282\",\n \"holiday.nationalDay\": \"\u56FD\u5E86\u8282\",\n \"holiday.springFestival\": \"\u6625\u8282\",\n \"holiday.qingming\": \"\u6E05\u660E\u8282\",\n \"holiday.dragonBoat\": \"\u7AEF\u5348\u8282\",\n \"holiday.labourDay\": \"\u52B3\u52A8\u8282\",\n}\n\nconst JA_T: Translation = {\n \"period.peak\": \"\u30D4\u30FC\u30AF\",\n \"period.offPeak\": \"\u30AA\u30D5\u30D4\u30FC\u30AF\",\n \"evidence.schedule\": \"\u516C\u5F0F\u30B9\u30B1\u30B8\u30E5\u30FC\u30EB\",\n \"evidence.apiHeader\": \"{header} \u7D4C\u7531\",\n \"evidence.cached\": \"\uFF08\u30AD\u30E3\u30C3\u30B7\u30E5\uFF09\",\n \"evidence.preview\": \"\uFF08\u307E\u3060\u30EA\u30AF\u30A8\u30B9\u30C8\u306A\u3057\uFF09\",\n \"evidence.holiday\": \"\u795D\u65E5\uFF1A{name}\",\n \"label.mismatch\": \"\u4E0D\u4E00\u81F4\",\n \"toast.title\": \"DeepSeek \u6599\u91D1\",\n \"status.title\": \"DeepSeek Peak\",\n \"status.noSession\": \"\u5148\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u958B\u3044\u3066\u304F\u3060\u3055\u3044\u3002\",\n \"status.noRequest\": \"\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u306F\u307E\u3060 DeepSeek \u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u3042\u308A\u307E\u305B\u3093\u3002\",\n \"status.source\": \"\u30BD\u30FC\u30B9\",\n \"status.evidence\": \"\u30A8\u30D3\u30C7\u30F3\u30B9\",\n \"status.unavailable\": \"DeepSeek Peak \u30B5\u30FC\u30D0\u30FC\u30D7\u30E9\u30B0\u30A4\u30F3\u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\",\n \"command.status.title\": \"DeepSeek Peak\uFF1A\u30B9\u30C6\u30FC\u30BF\u30B9\u8868\u793A\",\n \"command.status.description\": \"\u6700\u5F8C\u306B\u89B3\u6E2C\u3057\u305F DeepSeek \u6599\u91D1\u671F\u9593\u3092\u8868\u793A\",\n \"command.sections.title\": \"DeepSeek Peak\uFF1A\u30BB\u30AF\u30B7\u30E7\u30F3\",\n \"command.sections.description\": \"\u5BFE\u8A71\u5F0F\u30BB\u30AF\u30B7\u30E7\u30F3\u30E1\u30CB\u30E5\u30FC\u3092\u958B\u304F\uFF08Esc \u3067\u9589\u3058\u308B\uFF09\",\n \"command.lang.title\": \"DeepSeek Peak\uFF1A\u8A00\u8A9E\",\n \"command.lang.description\": \"\u8868\u793A\u8A00\u8A9E\u3092\u5207\u308A\u66FF\u3048\",\n \"settings.title\": \"DeepSeek Peak \u30BB\u30AF\u30B7\u30E7\u30F3\",\n \"settings.toast\": \"\u30C8\u30FC\u30B9\u30C8\",\n \"settings.footer\": \"\u30D5\u30C3\u30BF\u30FC\u8868\u793A\",\n \"settings.sidebar\": \"\u30B5\u30A4\u30C9\u30D0\u30FC\u8868\u793A\",\n \"settings.evidence\": \"\u30B5\u30A4\u30C9\u30D0\u30FC\u306E\u30A8\u30D3\u30C7\u30F3\u30B9\",\n \"settings.frequency\": \"\u30C8\u30FC\u30B9\u30C8\u983B\u5EA6\",\n \"settings.lang\": \"\u8A00\u8A9E\",\n \"settings.on\": \"\u30AA\u30F3\",\n \"settings.off\": \"\u30AA\u30D5\",\n \"frequency.change\": \"\u5909\u66F4\u6642\",\n \"frequency.every\": \"\u6BCE\u30EA\u30AF\u30A8\u30B9\u30C8\",\n \"holiday.midAutumn\": \"\u4E2D\u79CB\u7BC0\",\n \"holiday.nationalDay\": \"\u56FD\u6176\u7BC0\",\n \"holiday.springFestival\": \"\u6625\u7BC0\",\n \"holiday.qingming\": \"\u6E05\u660E\u7BC0\",\n \"holiday.dragonBoat\": \"\u7AEF\u5348\u7BC0\",\n \"holiday.labourDay\": \"\u52B4\u50CD\u7BC0\",\n}\n\nconst KO_T: Translation = {\n \"period.peak\": \"\uD53C\uD06C\",\n \"period.offPeak\": \"\uBE44\uD53C\uD06C\",\n \"evidence.schedule\": \"\uACF5\uC2DD \uC77C\uC815\",\n \"evidence.apiHeader\": \"{header} \uAE30\uC900\",\n \"evidence.cached\": \"(\uCE90\uC2DC)\",\n \"evidence.preview\": \"(\uC544\uC9C1 \uC694\uCCAD \uC5C6\uC74C)\",\n \"evidence.holiday\": \"\uACF5\uD734\uC77C: {name}\",\n \"label.mismatch\": \"\uBD88\uC77C\uCE58\",\n \"toast.title\": \"DeepSeek \uC694\uAE08\",\n \"status.title\": \"DeepSeek Peak\",\n \"status.noSession\": \"\uBA3C\uC800 \uC138\uC158\uC744 \uC5EC\uC138\uC694.\",\n \"status.noRequest\": \"\uC774 \uC138\uC158\uC5D0\uB294 \uC544\uC9C1 DeepSeek \uC694\uCCAD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\",\n \"status.source\": \"\uCD9C\uCC98\",\n \"status.evidence\": \"\uADFC\uAC70\",\n \"status.unavailable\": \"DeepSeek Peak \uC11C\uBC84 \uD50C\uB7EC\uADF8\uC778\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\",\n \"command.status.title\": \"DeepSeek Peak: \uC0C1\uD0DC \uD45C\uC2DC\",\n \"command.status.description\": \"\uB9C8\uC9C0\uB9C9\uC73C\uB85C \uAD00\uCE21\uB41C DeepSeek \uC694\uAE08 \uAE30\uAC04 \uD45C\uC2DC\",\n \"command.sections.title\": \"DeepSeek Peak: \uC139\uC158\",\n \"command.sections.description\": \"\uB300\uD654\uD615 \uC139\uC158 \uBA54\uB274 \uC5F4\uAE30 (Esc \uB2EB\uAE30)\",\n \"command.lang.title\": \"DeepSeek Peak: \uC5B8\uC5B4\",\n \"command.lang.description\": \"\uD45C\uC2DC \uC5B8\uC5B4 \uC804\uD658\",\n \"settings.title\": \"DeepSeek Peak \uC139\uC158\",\n \"settings.toast\": \"\uD1A0\uC2A4\uD2B8\",\n \"settings.footer\": \"\uD558\uB2E8 \uD45C\uC2DC\",\n \"settings.sidebar\": \"\uC0AC\uC774\uB4DC\uBC14 \uD45C\uC2DC\",\n \"settings.evidence\": \"\uC0AC\uC774\uB4DC\uBC14 \uADFC\uAC70\",\n \"settings.frequency\": \"\uD1A0\uC2A4\uD2B8 \uBE48\uB3C4\",\n \"settings.lang\": \"\uC5B8\uC5B4\",\n \"settings.on\": \"\uCF1C\uC9D0\",\n \"settings.off\": \"\uAEBC\uC9D0\",\n \"frequency.change\": \"\uBCC0\uACBD \uC2DC\",\n \"frequency.every\": \"\uB9E4 \uC694\uCCAD\",\n \"holiday.midAutumn\": \"\uCD94\uC11D\",\n \"holiday.nationalDay\": \"\uAD6D\uACBD\uC77C\",\n \"holiday.springFestival\": \"\uCD98\uC81C\",\n \"holiday.qingming\": \"\uCCAD\uBA85\uC808\",\n \"holiday.dragonBoat\": \"\uB2E8\uC624\uC808\",\n \"holiday.labourDay\": \"\uB178\uB3D9\uC808\",\n}\n\nconst TRANSLATIONS: Record<LangCode, Translation> = { en: EN_T, pt: PT_T, zh: ZH_T, ja: JA_T, ko: KO_T }\n\nexport const LANG_META: readonly { code: LangCode; label: string }[] = [\n { code: \"en\", label: \"English\" },\n { code: \"pt\", label: \"Portugu\u00EAs\" },\n { code: \"zh\", label: \"\u4E2D\u6587\" },\n { code: \"ja\", label: \"\u65E5\u672C\u8A9E\" },\n { code: \"ko\", label: \"\uD55C\uAD6D\uC5B4\" },\n]\n\n/** Infers the language from the environment (LANG/LC_ALL); defaults to English. */\nexport function detectLang(): LangCode {\n const raw = String(process.env.LANG ?? process.env.LC_ALL ?? \"\").toLowerCase()\n if (raw.includes(\"zh\")) return \"zh\"\n if (raw.includes(\"ja\")) return \"ja\"\n if (raw.includes(\"ko\")) return \"ko\"\n if (raw.includes(\"pt\")) return \"pt\"\n return \"en\"\n}\n\n/** Binds a translation function to the current language getter (reactive on change). */\nexport function createT(getLang: () => LangCode) {\n return (key: TranslationKey, params?: Record<string, string>) => {\n const table = TRANSLATIONS[getLang()] ?? EN_T\n const template = table[key] ?? EN_T[key] ?? String(key)\n if (!params) return template\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) => params[name] ?? match)\n }\n}\n\nexport type T = ReturnType<typeof createT>\n", "/** @jsxImportSource @opentui/solid */\n\nimport { createMemo, For, Show } from \"solid-js\"\nimport { themeColors } from \"../core/display.js\"\nimport { createT } from \"../i18n.js\"\nimport type { IndicatorProps } from \"./PeakPanel.js\"\n\n/** Prompt-footer status: peak stands out in the default text color, off-peak stays muted. */\nexport function FooterStatus(props: IndicatorProps) {\n const t = createT(() => props.lang())\n\n const segments = createMemo(() => {\n const status = props.status()\n if (!status) return []\n const { normal, muted, warning } = themeColors(props.context.theme)\n const peak = status.period === \"peak\"\n const label = peak ? t(\"period.peak\") : t(\"period.offPeak\")\n const out = [{ text: `DS ${label}`, color: peak ? normal : muted }]\n if (status.source === \"api-header\") out.push({ text: \" API\", color: muted })\n if (status.mismatch) out.push({ text: \" ⚠\", color: warning })\n out.push({ text: \" ·\", color: muted })\n return out\n })\n\n return (\n <Show when={props.enabled() && props.status()}>\n <text>\n <For each={segments()}>{(segment) => <span style={{ fg: segment.color }}>{segment.text}</span>}</For>\n </text>\n </Show>\n )\n}\n", "/** @jsxImportSource @opentui/solid */\n\nimport type { Plugin } from \"@opencode/plugin/tui\"\nimport { createEffect, createMemo, For, Show } from \"solid-js\"\nimport { evidenceLabel, themeColors } from \"../core/display.js\"\nimport { createT, type LangCode } from \"../i18n.js\"\nimport type { PeakStatus } from \"../types.js\"\n\nexport interface IndicatorProps {\n context: Plugin.Context\n status: () => PeakStatus | undefined\n enabled: () => boolean\n lang: () => LangCode\n sessionID: string\n refresh: (sessionID: string) => void\n}\n\nexport interface PanelProps extends IndicatorProps {\n evidence: () => boolean\n}\n\nexport function PeakPanel(props: PanelProps) {\n const t = createT(() => props.lang())\n\n // The server keeps the last observed request per session, so opening a\n // session must pull that status instead of waiting for the next event.\n createEffect(() => {\n const sessionID = props.sessionID\n if (sessionID) props.refresh(sessionID)\n })\n\n // Single compact status line, flush left, plus an optional muted evidence line.\n const segments = createMemo(() => {\n const status = props.status()\n if (!status) return []\n const { normal, muted, warning } = themeColors(props.context.theme)\n const peak = status.period === \"peak\"\n const out = [\n { text: \"DeepSeek \", color: muted },\n { text: `● ${t(peak ? \"period.peak\" : \"period.offPeak\")}`, color: peak ? normal : muted },\n ]\n if (status.source === \"api-header\") out.push({ text: \" · API\", color: muted })\n if (status.mismatch) out.push({ text: ` · ${t(\"label.mismatch\")} ⚠`, color: warning })\n return out\n })\n\n return (\n <Show when={props.enabled() && props.status()}>\n <box flexDirection=\"column\" alignItems=\"flex-start\" alignSelf=\"flex-start\">\n <text>\n <For each={segments()}>{(segment) => <span style={{ fg: segment.color }}>{segment.text}</span>}</For>\n </text>\n <Show when={props.evidence() ? props.status() : undefined}>\n {(status) => <text fg={themeColors(props.context.theme).muted}>{evidenceLabel(status(), t)}</text>}\n </Show>\n </box>\n </Show>\n )\n}\n", "import { Rpc } from \"@opencode/plugin/rpc\"\n\nconst statusSchema = {\n type: \"object\",\n properties: {\n sessionID: { type: \"string\" },\n providerID: { type: \"string\" },\n modelID: { type: \"string\" },\n period: { type: \"string\", enum: [\"peak\", \"off-peak\"] },\n scheduledPeriod: { type: \"string\", enum: [\"peak\", \"off-peak\"] },\n source: { type: \"string\", enum: [\"official-schedule\", \"api-header\"] },\n phase: { type: \"string\", enum: [\"request\", \"response\"] },\n mismatch: { type: \"boolean\" },\n observedAt: { type: \"number\" },\n evidence: { type: \"string\" },\n holiday: { type: \"string\" },\n },\n required: [\n \"sessionID\",\n \"providerID\",\n \"modelID\",\n \"period\",\n \"scheduledPeriod\",\n \"source\",\n \"phase\",\n \"mismatch\",\n \"observedAt\",\n \"evidence\",\n ],\n additionalProperties: false,\n} as const\n\nexport const DeepSeekPeakRpc = Rpc.define({\n id: \"deepseek-peak\",\n methods: {\n status: {\n input: {\n type: \"object\",\n properties: { sessionID: { type: \"string\" } },\n required: [\"sessionID\"],\n additionalProperties: false,\n },\n output: {\n type: \"object\",\n properties: {\n found: { type: \"boolean\" },\n status: statusSchema,\n },\n required: [\"found\"],\n additionalProperties: false,\n },\n },\n preview: {\n input: {\n type: \"object\",\n properties: { sessionID: { type: \"string\" } },\n required: [\"sessionID\"],\n additionalProperties: false,\n },\n output: {\n type: \"object\",\n properties: {\n found: { type: \"boolean\" },\n status: statusSchema,\n },\n required: [\"found\"],\n additionalProperties: false,\n },\n },\n },\n events: {\n updated: { schema: statusSchema },\n },\n})\n", "import type { Plugin } from \"@opencode/plugin/tui\"\nimport { createT, LANG_META, type LangCode } from \"../i18n.js\"\nimport type { TuiSettings } from \"../types.js\"\n\nconst nextTick = () => new Promise<void>((resolve) => setTimeout(resolve, 0))\n\n/**\n * Native sections menu: each pick applies and persists immediately, then the\n * menu reopens (the host closes the dialog before the next select). Cancelling\n * exits the menu.\n */\nexport function openSettingsMenu(\n context: Plugin.Context,\n settings: TuiSettings,\n update: (mutation: (draft: TuiSettings) => void) => Promise<void>,\n lang: () => LangCode,\n): void {\n const t = createT(lang)\n const onOff = (value: boolean) => (value ? t(\"settings.on\") : t(\"settings.off\"))\n\n const pickBool = (title: string, current: boolean) =>\n context.ui.dialog.select<boolean>({\n title,\n options: [\n { title: t(\"settings.on\"), value: true },\n { title: t(\"settings.off\"), value: false },\n ],\n current,\n })\n\n void (async () => {\n while (true) {\n const choice = await context.ui.dialog.select<string>({\n title: t(\"settings.title\"),\n options: [\n { title: `${t(\"settings.toast\")}: ${onOff(settings.toast)}`, value: \"toast\" },\n { title: `${t(\"settings.footer\")}: ${onOff(settings.indicator)}`, value: \"indicator\" },\n { title: `${t(\"settings.sidebar\")}: ${onOff(settings.sidebarIndicator !== false)}`, value: \"sidebar\" },\n { title: `${t(\"settings.evidence\")}: ${onOff(settings.sidebarEvidence !== false)}`, value: \"evidence\" },\n {\n title: `${t(\"settings.frequency\")}: ${settings.toastEveryRequest ? t(\"frequency.every\") : t(\"frequency.change\")}`,\n value: \"frequency\",\n },\n {\n title: `${t(\"settings.lang\")}: ${LANG_META.find((meta) => meta.code === lang())?.label ?? lang()}`,\n value: \"lang\",\n },\n ],\n })\n if (choice === undefined) return\n\n if (choice === \"toast\") {\n const picked = await pickBool(t(\"settings.toast\"), settings.toast)\n if (picked !== undefined) {\n await update((draft) => {\n draft.toast = picked\n })\n }\n } else if (choice === \"indicator\") {\n const picked = await pickBool(t(\"settings.footer\"), settings.indicator)\n if (picked !== undefined) {\n await update((draft) => {\n draft.indicator = picked\n })\n }\n } else if (choice === \"sidebar\") {\n const picked = await pickBool(t(\"settings.sidebar\"), settings.sidebarIndicator !== false)\n if (picked !== undefined) {\n await update((draft) => {\n draft.sidebarIndicator = picked\n })\n }\n } else if (choice === \"evidence\") {\n const picked = await pickBool(t(\"settings.evidence\"), settings.sidebarEvidence !== false)\n if (picked !== undefined) {\n await update((draft) => {\n draft.sidebarEvidence = picked\n })\n }\n } else if (choice === \"frequency\") {\n const picked = await context.ui.dialog.select<\"change\" | \"every\">({\n title: t(\"settings.frequency\"),\n options: [\n { title: t(\"frequency.change\"), value: \"change\" },\n { title: t(\"frequency.every\"), value: \"every\" },\n ],\n current: settings.toastEveryRequest ? \"every\" : \"change\",\n })\n if (picked !== undefined) {\n await update((draft) => {\n draft.toastEveryRequest = picked === \"every\"\n })\n }\n } else if (choice === \"lang\") {\n const picked = await context.ui.dialog.select<LangCode>({\n title: t(\"settings.lang\"),\n options: LANG_META.map((meta) => ({ title: meta.label, value: meta.code })),\n current: lang(),\n })\n if (picked !== undefined) {\n await update((draft) => {\n draft.lang = picked\n })\n }\n }\n\n await nextTick()\n }\n })()\n}\n", "import type { Plugin } from \"@opencode/plugin/tui\"\nimport { statusLabel } from \"../core/display.js\"\nimport { createT, LANG_META, type LangCode } from \"../i18n.js\"\nimport type { PeakStatus, TuiSettings } from \"../types.js\"\nimport { openSettingsMenu } from \"./settings-menu.js\"\n\ninterface CommandProps {\n context: Plugin.Context\n settings: TuiSettings\n update: (mutation: (draft: TuiSettings) => void) => Promise<void>\n readStatus: (sessionID: string) => Promise<PeakStatus | undefined>\n lang: () => LangCode\n}\n\n/** Command layer (registered in the app slot: slash commands stay available when the sidebar is hidden). */\nexport function CommandRoot(props: CommandProps) {\n const { context } = props\n const t = createT(() => props.lang())\n\n context.keymap.layer(() => ({\n mode: \"global\" as const,\n commands: [\n {\n id: \"opencode-deepseek-peak.peak.status\",\n title: t(\"command.status.title\"),\n description: t(\"command.status.description\"),\n group: \"DeepSeek\",\n palette: true,\n slash: { name: \"deepseek-peak\" },\n run: async () => {\n const route = context.ui.router.current()\n if (route.type !== \"session\") {\n await context.ui.dialog.alert({ title: t(\"status.title\"), message: t(\"status.noSession\") })\n return\n }\n try {\n const status = await props.readStatus(route.sessionID)\n await context.ui.dialog.alert({\n title: t(\"status.title\"),\n message: status\n ? `${statusLabel(status, t)}\\n${t(\"status.source\")}: ${status.source}\\n${t(\"status.evidence\")}: ${status.evidence}`\n : t(\"status.noRequest\"),\n })\n } catch {\n context.ui.toast.show({ message: t(\"status.unavailable\"), variant: \"error\" })\n }\n },\n },\n {\n id: \"opencode-deepseek-peak.peak.sections\",\n title: t(\"command.sections.title\"),\n description: t(\"command.sections.description\"),\n group: \"DeepSeek\",\n palette: true,\n slash: { name: \"deepseek-peak-sections\" },\n run: () => {\n openSettingsMenu(context, props.settings, props.update, props.lang)\n },\n },\n {\n id: \"opencode-deepseek-peak.peak.lang\",\n title: t(\"command.lang.title\"),\n description: t(\"command.lang.description\"),\n group: \"DeepSeek\",\n palette: true,\n slash: { name: \"deepseek-peak-lang\" },\n run: async () => {\n const picked = await context.ui.dialog.select<LangCode>({\n title: t(\"settings.lang\"),\n options: LANG_META.map((meta) => ({ title: meta.label, value: meta.code })),\n current: props.lang(),\n })\n if (picked !== undefined) {\n await props.update((draft) => {\n draft.lang = picked\n })\n }\n },\n },\n ],\n }))\n return null\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["createSignal", "createMemo", "For", "Show", "FooterStatus", "props", "t", "createT", "lang", "segments", "createMemo", "status", "normal", "muted", "warning", "themeColors", "context", "theme", "peak", "period", "label", "out", "text", "color", "source", "push", "mismatch", "_$createComponent", "Show", "when", "_$memo", "enabled", "children", "_el$", "_$createElement", "_$insert", "For", "each", "segment", "_el$2", "_$effect", "_$p", "_$setProp", "fg", "createEffect", "createMemo", "For", "Show", "PeakPanel", "props", "t", "createT", "lang", "createEffect", "sessionID", "refresh", "segments", "createMemo", "status", "normal", "muted", "warning", "themeColors", "context", "theme", "peak", "period", "out", "text", "color", "source", "push", "mismatch", "_$createComponent", "Show", "when", "_$memo", "enabled", "children", "_el$", "_$createElement", "_el$2", "_$insertNode", "_$setProp", "_$insert", "For", "each", "segment", "_el$3", "_$effect", "_$p", "fg", "evidence", "undefined", "_el$4", "evidenceLabel", "mod", "id", "setup", "context", "client", "rpc", "DeepSeekPeakRpc", "settings", "update", "storage", "store", "initial", "INITIAL_SETTINGS", "lang", "detectLang", "t", "createT", "statuses", "setStatuses", "createSignal", "lastToast", "Map", "unsubscribe", "events", "on", "event", "status", "data", "current", "sessionID", "toast", "fingerprint", "period", "mismatch", "toastEveryRequest", "get", "set", "ui", "show", "title", "message", "statusLabel", "variant", "duration", "readStatus", "result", "found", "preview", "undefined", "refresh", "then", "catch", "unregisterCommands", "slot", "append", "render", "_$createComponent", "CommandRoot", "unregisterSidebar", "props", "String", "PeakPanel", "enabled", "sidebarIndicator", "evidence", "sidebarEvidence", "unregisterFooter", "FooterStatus", "indicator"]
7
+ }
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default } from "./dist/index.js"
package/install.mjs ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
4
+ import { homedir, platform } from "node:os"
5
+ import { join } from "node:path"
6
+ import { applyEdits, modify, parse } from "jsonc-parser"
7
+
8
+ const PLUGIN_SPEC = "opencode-deepseek-peak"
9
+ const base = platform() === "win32"
10
+ ? process.env.APPDATA ?? join(homedir(), "AppData", "Roaming")
11
+ : process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config")
12
+ const directory = join(base, "opencode")
13
+
14
+ async function readOptional(path) {
15
+ try {
16
+ return await readFile(path, "utf8")
17
+ } catch (error) {
18
+ if (error.code === "ENOENT") return undefined
19
+ throw error
20
+ }
21
+ }
22
+
23
+ async function install() {
24
+ await mkdir(directory, { recursive: true })
25
+ const jsoncPath = join(directory, "opencode.jsonc")
26
+ const jsonc = await readOptional(jsoncPath)
27
+ const targets = [
28
+ [jsonc !== undefined ? jsoncPath : join(directory, "opencode.json"), "https://opencode.ai/config.json"],
29
+ [join(directory, "cli.json"), "https://opencode.ai/v2/cli.json"],
30
+ ]
31
+ // Validate both documents before writing either one. JSONC edits preserve
32
+ // comments, trailing commas and unrelated settings.
33
+ const updates = []
34
+ for (const [path, schema] of targets) {
35
+ const text = await readOptional(path) ?? JSON.stringify({ $schema: schema, plugins: [] }, null, 2) + "\n"
36
+ const errors = []
37
+ const config = parse(text, errors, { allowTrailingComma: true })
38
+ if (errors.length || !config || typeof config !== "object" || Array.isArray(config)) {
39
+ throw new Error(`Invalid configuration: ${path}`)
40
+ }
41
+ const plugins = config.plugins ?? []
42
+ if (!Array.isArray(plugins)) throw new Error(`Expected a plugins array: ${path}`)
43
+ const present = plugins.some((item) => {
44
+ const spec = typeof item === "string" ? item : item?.package
45
+ return spec === PLUGIN_SPEC || spec?.startsWith(`${PLUGIN_SPEC}@`)
46
+ })
47
+ if (present) continue
48
+ const result = applyEdits(text, modify(text, ["plugins"], [...plugins, PLUGIN_SPEC], {
49
+ formattingOptions: { insertSpaces: true, tabSize: 2 },
50
+ }))
51
+ updates.push([path, result])
52
+ }
53
+ for (const [path, text] of updates) {
54
+ await writeFile(path, text)
55
+ console.log(`[${PLUGIN_SPEC}] Added to ${path}`)
56
+ }
57
+ console.log(updates.length ? "Restart OpenCode to load DeepSeek Peak." : "DeepSeek Peak is already configured.")
58
+ }
59
+
60
+ install().catch((error) => {
61
+ console.error("Install failed:", error.message)
62
+ process.exitCode = 1
63
+ })
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "opencode-deepseek-peak",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode V2 plugin showing DeepSeek peak and off-peak pricing periods",
5
+ "type": "module",
6
+ "keywords": [
7
+ "opencode",
8
+ "opencode-plugin",
9
+ "deepseek",
10
+ "pricing",
11
+ "peak",
12
+ "off-peak",
13
+ "tui"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/aiev/opencode-deepseek-peak.git"
18
+ },
19
+ "license": "MIT",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./index.js"
23
+ },
24
+ "./rpc": {
25
+ "import": "./dist/rpc.js"
26
+ },
27
+ "./tui": {
28
+ "import": "./tui/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "index.js",
33
+ "dist",
34
+ "src",
35
+ "tui",
36
+ "install.mjs",
37
+ "README.md"
38
+ ],
39
+ "bin": {
40
+ "opencode-deepseek-peak": "install.mjs"
41
+ },
42
+ "scripts": {
43
+ "build": "node build.mjs",
44
+ "build:tui": "node build.tui.mjs",
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "tsx --test tests/*.test.ts",
47
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
48
+ },
49
+ "dependencies": {
50
+ "date-holidays": "^3.26.5",
51
+ "jsonc-parser": "^3.3.1"
52
+ },
53
+ "devDependencies": {
54
+ "@opencode/plugin": "^2.0.16",
55
+ "@opentui/core": "^0.5.10",
56
+ "@opentui/solid": "^0.5.10",
57
+ "@types/node": "^22.0.0",
58
+ "esbuild": "^0.25.0",
59
+ "esbuild-plugin-solid": "^0.6.0",
60
+ "solid-js": "^1.9.0",
61
+ "tsx": "^4.22.3",
62
+ "typescript": "^5.8.0"
63
+ },
64
+ "peerDependencies": {
65
+ "@opencode/plugin": ">=2.0.0",
66
+ "@opentui/core": ">=0.5.8",
67
+ "@opentui/solid": ">=0.5.8",
68
+ "solid-js": ">=1.9.0"
69
+ }
70
+ }
@@ -0,0 +1,32 @@
1
+ import type { PricePeriod } from "./types.js"
2
+
3
+ export const DEFAULT_SIGNAL_HEADERS = [
4
+ "x-deepseek-pricing-tier",
5
+ "x-deepseek-price-period",
6
+ "x-deepseek-peak",
7
+ "x-pricing-tier",
8
+ ]
9
+
10
+ const PEAK_VALUES = new Set(["peak", "peak-hours", "peak_hours", "true", "1", "on"])
11
+ const OFF_PEAK_VALUES = new Set([
12
+ "off-peak",
13
+ "off_peak",
14
+ "offpeak",
15
+ "false",
16
+ "0",
17
+ "off",
18
+ ])
19
+
20
+ export function readApiPricePeriod(
21
+ headers: Headers,
22
+ names: readonly string[] = DEFAULT_SIGNAL_HEADERS,
23
+ ): { period: PricePeriod; evidence: string } | undefined {
24
+ for (const name of names) {
25
+ const raw = headers.get(name)
26
+ if (!raw) continue
27
+ const value = raw.trim().toLowerCase()
28
+ if (PEAK_VALUES.has(value)) return { period: "peak", evidence: `${name}: ${raw}` }
29
+ if (OFF_PEAK_VALUES.has(value)) return { period: "off-peak", evidence: `${name}: ${raw}` }
30
+ }
31
+ return undefined
32
+ }
@@ -0,0 +1,59 @@
1
+ import type { Plugin } from "@opencode/plugin/tui"
2
+ import type { T, TranslationKey } from "../i18n.js"
3
+ import type { PeakStatus, TuiSettings } from "../types.js"
4
+
5
+ export const INITIAL_SETTINGS: TuiSettings = {
6
+ toast: true,
7
+ indicator: true,
8
+ sidebarIndicator: true,
9
+ sidebarEvidence: true,
10
+ toastEveryRequest: false,
11
+ lang: "en",
12
+ }
13
+
14
+ export function statusLabel(status: PeakStatus, t: T) {
15
+ const period = t(status.period === "peak" ? "period.peak" : "period.offPeak")
16
+ const correction = status.mismatch ? ` · API ≠ ${t("evidence.schedule")}` : ""
17
+ return `DeepSeek ${period}${correction}`
18
+ }
19
+
20
+ const HOLIDAY_KEYS: Record<string, TranslationKey> = {
21
+ "Mid-Autumn Festival": "holiday.midAutumn",
22
+ "National Day": "holiday.nationalDay",
23
+ "Spring Festival": "holiday.springFestival",
24
+ "Qingming Festival": "holiday.qingming",
25
+ "Dragon Boat Festival": "holiday.dragonBoat",
26
+ "Labour Day": "holiday.labourDay",
27
+ }
28
+
29
+ /** Short, sidebar-friendly, localized version of the status evidence. */
30
+ export function evidenceLabel(status: PeakStatus, t: T): string {
31
+ let label: string
32
+ // An API response header is the source of truth when present.
33
+ if (status.source === "api-header") {
34
+ label = t("evidence.apiHeader", { header: status.evidence.split(":")[0] })
35
+ } else if (status.holiday) {
36
+ const key = HOLIDAY_KEYS[status.holiday]
37
+ label = t("evidence.holiday", { name: key ? t(key) : status.holiday })
38
+ } else {
39
+ label = t("evidence.schedule")
40
+ }
41
+ if (status.evidence.endsWith("(cached)")) label += ` ${t("evidence.cached")}`
42
+ if (status.evidence.includes("no request observed yet")) label += ` ${t("evidence.preview")}`
43
+ return label
44
+ }
45
+
46
+ /**
47
+ * Theme text tokens have two generations (base/muted and default/subdued), and
48
+ * the feedback colors do too. Prefer the newer names and fall back, matching
49
+ * the sibling plugins' mapping.
50
+ */
51
+ export function themeColors(theme: Plugin.Context["theme"]) {
52
+ const text = theme.text
53
+ const warning = text.feedback.warning
54
+ return {
55
+ normal: text.base ?? text.default,
56
+ muted: text.muted ?? text.subdued,
57
+ warning: warning.base ?? warning.default,
58
+ }
59
+ }
package/src/i18n.ts ADDED
@@ -0,0 +1,248 @@
1
+ // ---------------------------------------------------------------------------
2
+ // i18n — centralized translations.
3
+ // Add a language by appending a table that satisfies `Translation`; the
4
+ // compiler enforces key completeness. Translation tables are product data and
5
+ // stay localized; code comments are English.
6
+ // ---------------------------------------------------------------------------
7
+
8
+ export type LangCode = "en" | "pt" | "zh" | "ja" | "ko"
9
+
10
+ const EN_T = {
11
+ "period.peak": "PEAK",
12
+ "period.offPeak": "OFF-PEAK",
13
+ "evidence.schedule": "official schedule",
14
+ "evidence.apiHeader": "via {header}",
15
+ "evidence.cached": "(cached)",
16
+ "evidence.preview": "(no request yet)",
17
+ "evidence.holiday": "Holiday: {name}",
18
+ "label.mismatch": "mismatch",
19
+ "toast.title": "DeepSeek pricing",
20
+ "status.title": "DeepSeek Peak",
21
+ "status.noSession": "Open a session first.",
22
+ "status.noRequest": "No DeepSeek request observed in this session yet.",
23
+ "status.source": "Source",
24
+ "status.evidence": "Evidence",
25
+ "status.unavailable": "DeepSeek Peak server plugin is unavailable.",
26
+ "command.status.title": "DeepSeek Peak: Show Status",
27
+ "command.status.description": "Show the last observed DeepSeek pricing period",
28
+ "command.sections.title": "DeepSeek Peak: Sections",
29
+ "command.sections.description": "Open the interactive sections menu (Esc to close)",
30
+ "command.lang.title": "DeepSeek Peak: Language",
31
+ "command.lang.description": "Switch display language",
32
+ "settings.title": "DeepSeek Peak sections",
33
+ "settings.toast": "Toast",
34
+ "settings.footer": "Footer indicator",
35
+ "settings.sidebar": "Sidebar indicator",
36
+ "settings.evidence": "Sidebar evidence",
37
+ "settings.frequency": "Toast frequency",
38
+ "settings.lang": "Language",
39
+ "settings.on": "on",
40
+ "settings.off": "off",
41
+ "frequency.change": "on change",
42
+ "frequency.every": "every request",
43
+ "holiday.midAutumn": "Mid-Autumn Festival",
44
+ "holiday.nationalDay": "National Day",
45
+ "holiday.springFestival": "Spring Festival",
46
+ "holiday.qingming": "Qingming Festival",
47
+ "holiday.dragonBoat": "Dragon Boat Festival",
48
+ "holiday.labourDay": "Labour Day",
49
+ }
50
+
51
+ export type Translation = typeof EN_T
52
+ export type TranslationKey = keyof Translation
53
+
54
+ const PT_T: Translation = {
55
+ "period.peak": "PICO",
56
+ "period.offPeak": "FORA DE PICO",
57
+ "evidence.schedule": "horário oficial",
58
+ "evidence.apiHeader": "via {header}",
59
+ "evidence.cached": "(cache)",
60
+ "evidence.preview": "(sem requisição ainda)",
61
+ "evidence.holiday": "Feriado: {name}",
62
+ "label.mismatch": "divergente",
63
+ "toast.title": "Preço DeepSeek",
64
+ "status.title": "DeepSeek Peak",
65
+ "status.noSession": "Abra uma sessão primeiro.",
66
+ "status.noRequest": "Nenhuma requisição DeepSeek observada nesta sessão ainda.",
67
+ "status.source": "Fonte",
68
+ "status.evidence": "Evidência",
69
+ "status.unavailable": "O plugin de servidor do DeepSeek Peak está indisponível.",
70
+ "command.status.title": "DeepSeek Peak: Mostrar status",
71
+ "command.status.description": "Mostra o último período de preço observado da DeepSeek",
72
+ "command.sections.title": "DeepSeek Peak: Seções",
73
+ "command.sections.description": "Abre o menu interativo de seções (Esc para fechar)",
74
+ "command.lang.title": "DeepSeek Peak: Idioma",
75
+ "command.lang.description": "Troca o idioma da interface",
76
+ "settings.title": "Seções do DeepSeek Peak",
77
+ "settings.toast": "Toast",
78
+ "settings.footer": "Indicador no rodapé",
79
+ "settings.sidebar": "Indicador na barra lateral",
80
+ "settings.evidence": "Evidência na barra lateral",
81
+ "settings.frequency": "Frequência do toast",
82
+ "settings.lang": "Idioma",
83
+ "settings.on": "ativado",
84
+ "settings.off": "desativado",
85
+ "frequency.change": "ao mudar",
86
+ "frequency.every": "a cada requisição",
87
+ "holiday.midAutumn": "Festival do Meio do Outono",
88
+ "holiday.nationalDay": "Dia Nacional",
89
+ "holiday.springFestival": "Festival da Primavera",
90
+ "holiday.qingming": "Festival Qingming",
91
+ "holiday.dragonBoat": "Festival do Barco Dragão",
92
+ "holiday.labourDay": "Dia do Trabalho",
93
+ }
94
+
95
+ const ZH_T: Translation = {
96
+ "period.peak": "高峰",
97
+ "period.offPeak": "低谷",
98
+ "evidence.schedule": "官方时段",
99
+ "evidence.apiHeader": "来自 {header}",
100
+ "evidence.cached": "(缓存)",
101
+ "evidence.preview": "(尚无请求)",
102
+ "evidence.holiday": "节假日:{name}",
103
+ "label.mismatch": "不一致",
104
+ "toast.title": "DeepSeek 价格",
105
+ "status.title": "DeepSeek Peak",
106
+ "status.noSession": "请先打开一个会话。",
107
+ "status.noRequest": "此会话尚未观察到 DeepSeek 请求。",
108
+ "status.source": "来源",
109
+ "status.evidence": "证据",
110
+ "status.unavailable": "DeepSeek Peak 服务端插件不可用。",
111
+ "command.status.title": "DeepSeek Peak:显示状态",
112
+ "command.status.description": "显示最近观察到的 DeepSeek 价格时段",
113
+ "command.sections.title": "DeepSeek Peak:板块",
114
+ "command.sections.description": "打开交互式板块菜单(Esc 关闭)",
115
+ "command.lang.title": "DeepSeek Peak:语言",
116
+ "command.lang.description": "切换界面语言",
117
+ "settings.title": "DeepSeek Peak 板块",
118
+ "settings.toast": "通知",
119
+ "settings.footer": "底部指示器",
120
+ "settings.sidebar": "侧边栏指示器",
121
+ "settings.evidence": "侧边栏证据",
122
+ "settings.frequency": "通知频率",
123
+ "settings.lang": "语言",
124
+ "settings.on": "开",
125
+ "settings.off": "关",
126
+ "frequency.change": "变化时",
127
+ "frequency.every": "每次请求",
128
+ "holiday.midAutumn": "中秋节",
129
+ "holiday.nationalDay": "国庆节",
130
+ "holiday.springFestival": "春节",
131
+ "holiday.qingming": "清明节",
132
+ "holiday.dragonBoat": "端午节",
133
+ "holiday.labourDay": "劳动节",
134
+ }
135
+
136
+ const JA_T: Translation = {
137
+ "period.peak": "ピーク",
138
+ "period.offPeak": "オフピーク",
139
+ "evidence.schedule": "公式スケジュール",
140
+ "evidence.apiHeader": "{header} 経由",
141
+ "evidence.cached": "(キャッシュ)",
142
+ "evidence.preview": "(まだリクエストなし)",
143
+ "evidence.holiday": "祝日:{name}",
144
+ "label.mismatch": "不一致",
145
+ "toast.title": "DeepSeek 料金",
146
+ "status.title": "DeepSeek Peak",
147
+ "status.noSession": "先にセッションを開いてください。",
148
+ "status.noRequest": "このセッションではまだ DeepSeek リクエストがありません。",
149
+ "status.source": "ソース",
150
+ "status.evidence": "エビデンス",
151
+ "status.unavailable": "DeepSeek Peak サーバープラグインが利用できません。",
152
+ "command.status.title": "DeepSeek Peak:ステータス表示",
153
+ "command.status.description": "最後に観測した DeepSeek 料金期間を表示",
154
+ "command.sections.title": "DeepSeek Peak:セクション",
155
+ "command.sections.description": "対話式セクションメニューを開く(Esc で閉じる)",
156
+ "command.lang.title": "DeepSeek Peak:言語",
157
+ "command.lang.description": "表示言語を切り替え",
158
+ "settings.title": "DeepSeek Peak セクション",
159
+ "settings.toast": "トースト",
160
+ "settings.footer": "フッター表示",
161
+ "settings.sidebar": "サイドバー表示",
162
+ "settings.evidence": "サイドバーのエビデンス",
163
+ "settings.frequency": "トースト頻度",
164
+ "settings.lang": "言語",
165
+ "settings.on": "オン",
166
+ "settings.off": "オフ",
167
+ "frequency.change": "変更時",
168
+ "frequency.every": "毎リクエスト",
169
+ "holiday.midAutumn": "中秋節",
170
+ "holiday.nationalDay": "国慶節",
171
+ "holiday.springFestival": "春節",
172
+ "holiday.qingming": "清明節",
173
+ "holiday.dragonBoat": "端午節",
174
+ "holiday.labourDay": "労働節",
175
+ }
176
+
177
+ const KO_T: Translation = {
178
+ "period.peak": "피크",
179
+ "period.offPeak": "비피크",
180
+ "evidence.schedule": "공식 일정",
181
+ "evidence.apiHeader": "{header} 기준",
182
+ "evidence.cached": "(캐시)",
183
+ "evidence.preview": "(아직 요청 없음)",
184
+ "evidence.holiday": "공휴일: {name}",
185
+ "label.mismatch": "불일치",
186
+ "toast.title": "DeepSeek 요금",
187
+ "status.title": "DeepSeek Peak",
188
+ "status.noSession": "먼저 세션을 여세요.",
189
+ "status.noRequest": "이 세션에는 아직 DeepSeek 요청이 없습니다.",
190
+ "status.source": "출처",
191
+ "status.evidence": "근거",
192
+ "status.unavailable": "DeepSeek Peak 서버 플러그인을 사용할 수 없습니다.",
193
+ "command.status.title": "DeepSeek Peak: 상태 표시",
194
+ "command.status.description": "마지막으로 관측된 DeepSeek 요금 기간 표시",
195
+ "command.sections.title": "DeepSeek Peak: 섹션",
196
+ "command.sections.description": "대화형 섹션 메뉴 열기 (Esc 닫기)",
197
+ "command.lang.title": "DeepSeek Peak: 언어",
198
+ "command.lang.description": "표시 언어 전환",
199
+ "settings.title": "DeepSeek Peak 섹션",
200
+ "settings.toast": "토스트",
201
+ "settings.footer": "하단 표시",
202
+ "settings.sidebar": "사이드바 표시",
203
+ "settings.evidence": "사이드바 근거",
204
+ "settings.frequency": "토스트 빈도",
205
+ "settings.lang": "언어",
206
+ "settings.on": "켜짐",
207
+ "settings.off": "꺼짐",
208
+ "frequency.change": "변경 시",
209
+ "frequency.every": "매 요청",
210
+ "holiday.midAutumn": "추석",
211
+ "holiday.nationalDay": "국경일",
212
+ "holiday.springFestival": "춘제",
213
+ "holiday.qingming": "청명절",
214
+ "holiday.dragonBoat": "단오절",
215
+ "holiday.labourDay": "노동절",
216
+ }
217
+
218
+ const TRANSLATIONS: Record<LangCode, Translation> = { en: EN_T, pt: PT_T, zh: ZH_T, ja: JA_T, ko: KO_T }
219
+
220
+ export const LANG_META: readonly { code: LangCode; label: string }[] = [
221
+ { code: "en", label: "English" },
222
+ { code: "pt", label: "Português" },
223
+ { code: "zh", label: "中文" },
224
+ { code: "ja", label: "日本語" },
225
+ { code: "ko", label: "한국어" },
226
+ ]
227
+
228
+ /** Infers the language from the environment (LANG/LC_ALL); defaults to English. */
229
+ export function detectLang(): LangCode {
230
+ const raw = String(process.env.LANG ?? process.env.LC_ALL ?? "").toLowerCase()
231
+ if (raw.includes("zh")) return "zh"
232
+ if (raw.includes("ja")) return "ja"
233
+ if (raw.includes("ko")) return "ko"
234
+ if (raw.includes("pt")) return "pt"
235
+ return "en"
236
+ }
237
+
238
+ /** Binds a translation function to the current language getter (reactive on change). */
239
+ export function createT(getLang: () => LangCode) {
240
+ return (key: TranslationKey, params?: Record<string, string>) => {
241
+ const table = TRANSLATIONS[getLang()] ?? EN_T
242
+ const template = table[key] ?? EN_T[key] ?? String(key)
243
+ if (!params) return template
244
+ return template.replace(/\{(\w+)\}/g, (match, name: string) => params[name] ?? match)
245
+ }
246
+ }
247
+
248
+ export type T = ReturnType<typeof createT>