opencode-tbot 0.1.0 โ†’ 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.js CHANGED
@@ -1,6 +1,7 @@
1
- import { i as preparePluginConfiguration, s as loadAppConfig } from "./assets/plugin-config-Crgl_PZz.js";
1
+ import { i as preparePluginConfiguration, s as loadAppConfig } from "./assets/plugin-config-BYsYAzvx.js";
2
2
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, extname } from "node:path";
4
+ import { parse, printParseErrorCode } from "jsonc-parser";
4
5
  import { z } from "zod";
5
6
  import { OpenRouter } from "@openrouter/sdk";
6
7
  import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
@@ -243,7 +244,14 @@ var OpenCodeClient = class {
243
244
  this.fetchFn = fetchFn;
244
245
  }
245
246
  async getHealth() {
246
- return unwrapSdkData(await this.client.global.health(SDK_OPTIONS));
247
+ const healthEndpoint = this.client.global?.health;
248
+ if (typeof healthEndpoint === "function") return unwrapSdkData(await healthEndpoint.call(this.client.global, SDK_OPTIONS));
249
+ const rawClient = getRawSdkClient(this.client);
250
+ if (!rawClient) throw new Error("OpenCode SDK client does not expose a compatible health endpoint.");
251
+ return unwrapSdkData(await rawClient.get({
252
+ url: "/global/health",
253
+ ...SDK_OPTIONS
254
+ }));
247
255
  }
248
256
  async abortSession(sessionId) {
249
257
  return unwrapSdkData(await this.client.session.abort({ sessionID: sessionId }, SDK_OPTIONS));
@@ -490,6 +498,9 @@ function unwrapSdkData(response) {
490
498
  if (response && typeof response === "object" && "data" in response) return response.data;
491
499
  return response;
492
500
  }
501
+ function getRawSdkClient(client) {
502
+ return client.client ?? client._client ?? null;
503
+ }
493
504
  async function resolveProviderAvailability(config, fetchFn) {
494
505
  const configuredProviders = Object.entries(config.provider ?? {});
495
506
  const availabilityEntries = await Promise.all(configuredProviders.map(async ([providerId, providerConfig]) => [providerId, await fetchProviderAvailableModelIds(providerConfig, fetchFn)]));
@@ -582,7 +593,7 @@ async function readStateFile(filePath, createDefaultState) {
582
593
  const content = await readFile(filePath, "utf8");
583
594
  return JSON.parse(content);
584
595
  } catch (error) {
585
- if (isMissingFileError(error)) return createDefaultState();
596
+ if (isMissingFileError$1(error)) return createDefaultState();
586
597
  throw error;
587
598
  }
588
599
  }
@@ -595,7 +606,7 @@ async function writeStateFile(filePath, state) {
595
606
  function cloneState(state) {
596
607
  return JSON.parse(JSON.stringify(state));
597
608
  }
598
- function isMissingFileError(error) {
609
+ function isMissingFileError$1(error) {
599
610
  return error instanceof Error && "code" in error && error.code === "ENOENT";
600
611
  }
601
612
  //#endregion
@@ -962,9 +973,11 @@ var GetStatusUseCase = class {
962
973
  this.listLspUseCase.execute({ chatId: input.chatId }),
963
974
  this.listMcpUseCase.execute({ chatId: input.chatId })
964
975
  ]);
976
+ const pathResult = mapSettledResult(path);
965
977
  return {
966
978
  health: mapSettledResult(health),
967
- path: mapSettledResult(path),
979
+ path: pathResult,
980
+ plugins: await loadConfiguredPluginsResult(pathResult),
968
981
  lsp: mapSettledResult(lsp),
969
982
  mcp: mapSettledResult(mcp)
970
983
  };
@@ -980,6 +993,53 @@ function mapSettledResult(result) {
980
993
  status: "error"
981
994
  };
982
995
  }
996
+ async function loadConfiguredPluginsResult(path) {
997
+ if (path.status === "error") return {
998
+ error: path.error,
999
+ status: "error"
1000
+ };
1001
+ try {
1002
+ return {
1003
+ data: await loadConfiguredPlugins(path.data.config),
1004
+ status: "ok"
1005
+ };
1006
+ } catch (error) {
1007
+ return {
1008
+ error,
1009
+ status: "error"
1010
+ };
1011
+ }
1012
+ }
1013
+ async function loadConfiguredPlugins(configFilePath) {
1014
+ let content;
1015
+ try {
1016
+ content = await readFile(configFilePath, "utf8");
1017
+ } catch (error) {
1018
+ if (isMissingFileError(error)) return {
1019
+ configFilePath,
1020
+ plugins: []
1021
+ };
1022
+ throw error;
1023
+ }
1024
+ const parseErrors = [];
1025
+ const parsed = parse(content, parseErrors, { allowTrailingComma: true });
1026
+ if (parseErrors.length > 0) {
1027
+ const errorSummary = parseErrors.map((error) => printParseErrorCode(error.error)).join(", ");
1028
+ throw new Error(`Failed to parse ${configFilePath}: ${errorSummary}`);
1029
+ }
1030
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
1031
+ configFilePath,
1032
+ plugins: []
1033
+ };
1034
+ const pluginSpecs = Array.isArray(parsed.plugin) ? parsed.plugin : [];
1035
+ return {
1036
+ configFilePath,
1037
+ plugins: [...new Set(pluginSpecs.filter((value) => typeof value === "string").map((value) => value.trim()).filter((value) => value.length > 0))]
1038
+ };
1039
+ }
1040
+ function isMissingFileError(error) {
1041
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
1042
+ }
983
1043
  function resolveSelectedAgent(agents, requestedAgentName) {
984
1044
  const visibleAgents = agents.filter((agent) => !agent.hidden);
985
1045
  if (requestedAgentName) {
@@ -2519,6 +2579,7 @@ function presentStatusMessage(input, copy = BOT_COPY) {
2519
2579
  const sections = [
2520
2580
  presentStatusPlainSection(layout.serverTitle, presentStatusPlainServerLines(input, copy, layout)),
2521
2581
  presentStatusPlainSection(layout.workspaceTitle, presentStatusPlainWorkspaceLines(input, copy, layout)),
2582
+ presentStatusPlainSection(layout.pluginsTitle, presentStatusPlainPluginLines(input, copy, layout)),
2522
2583
  presentStatusPlainSection(layout.mcpTitle, presentStatusPlainMcpLines(input, copy, layout)),
2523
2584
  presentStatusPlainSection(layout.lspTitle, presentStatusPlainLspLines(input, copy, layout)),
2524
2585
  layout.divider,
@@ -2531,6 +2592,7 @@ function presentStatusMarkdownMessage(input, copy = BOT_COPY) {
2531
2592
  const sections = [
2532
2593
  presentStatusMarkdownSection(layout.serverTitle, presentStatusMarkdownServerLines(input, copy, layout)),
2533
2594
  presentStatusMarkdownSection(layout.workspaceTitle, presentStatusMarkdownWorkspaceLines(input, copy, layout)),
2595
+ presentStatusMarkdownSection(layout.pluginsTitle, presentStatusMarkdownPluginLines(input, copy, layout)),
2534
2596
  presentStatusMarkdownSection(layout.mcpTitle, presentStatusMarkdownMcpLines(input, copy, layout)),
2535
2597
  presentStatusMarkdownSection(layout.lspTitle, presentStatusMarkdownLspLines(input, copy, layout)),
2536
2598
  layout.divider,
@@ -2567,90 +2629,71 @@ function presentStatusMarkdownWorkspaceLines(input, copy, layout) {
2567
2629
  if (input.path.status === "error") return presentStatusMarkdownErrorLines(input.path.error, copy, layout);
2568
2630
  return [presentMarkdownStatusBullet(layout.currentDirectoryLabel, input.path.data.directory, { codeValue: true })];
2569
2631
  }
2570
- function presentStatusPlainLspLines(input, copy, layout) {
2571
- return buildPlainStatusTable([
2572
- layout.languageLabel,
2573
- layout.statusLabel,
2574
- layout.rootLabel
2575
- ], buildLspStatusRows(input, copy, layout));
2576
- }
2577
- function presentStatusMarkdownLspLines(input, copy, layout) {
2578
- return buildMarkdownStatusTable([
2579
- layout.languageLabel,
2580
- layout.statusLabel,
2581
- layout.rootLabel
2582
- ], buildLspStatusRows(input, copy, layout));
2583
- }
2584
- function presentStatusPlainMcpLines(input, copy, layout) {
2585
- return buildPlainStatusTable([
2586
- layout.serviceLabel,
2587
- layout.statusLabel,
2588
- layout.mcpNotesLabel
2589
- ], buildMcpStatusRows(input, copy, layout));
2590
- }
2591
- function presentStatusMarkdownMcpLines(input, copy, layout) {
2592
- return buildMarkdownStatusTable([
2593
- layout.serviceLabel,
2594
- layout.statusLabel,
2595
- layout.mcpNotesLabel
2596
- ], buildMcpStatusRows(input, copy, layout));
2597
- }
2598
- function buildLspStatusRows(input, copy, layout) {
2599
- if (input.lsp.status === "error") return [[
2600
- "-",
2601
- layout.errorStatus,
2602
- flattenStatusError(input.lsp.error, copy)
2603
- ]];
2604
- if (input.lsp.data.statuses.length === 0) return [[
2605
- "-",
2606
- layout.noneStatus,
2607
- copy.lsp.none
2608
- ]];
2609
- return input.lsp.data.statuses.map((status) => [
2610
- status.name,
2611
- formatLspStatusBadge(status, copy),
2612
- status.root || "-"
2613
- ]);
2614
- }
2615
- function buildMcpStatusRows(input, copy, layout) {
2616
- if (input.mcp.status === "error") return [[
2617
- "-",
2618
- layout.errorStatus,
2619
- flattenStatusError(input.mcp.error, copy)
2620
- ]];
2621
- if (input.mcp.data.statuses.length === 0) return [[
2622
- "-",
2623
- layout.noneStatus,
2624
- copy.mcp.none
2625
- ]];
2626
- return input.mcp.data.statuses.map(({ name, status }) => [
2627
- name,
2628
- formatMcpStatusBadge(status, copy, layout),
2629
- formatMcpStatusNotes(status, layout)
2630
- ]);
2631
- }
2632
- function buildPlainStatusTable(headers, rows) {
2632
+ function presentStatusPlainPluginLines(input, copy, layout) {
2633
+ if (input.plugins.status === "error") return presentStatusPlainErrorLines(input.plugins.error, copy, layout);
2634
+ const lines = [presentPlainStatusBullet(layout.configFileLabel, input.plugins.data.configFilePath)];
2635
+ if (input.plugins.data.plugins.length === 0) return [...lines, ...presentPlainEmptyStatusLines(layout.noPluginsMessage, layout)];
2633
2636
  return [
2634
- formatPlainTableRow(headers),
2635
- formatPlainTableSeparator(headers.length),
2636
- ...rows.map((row) => formatPlainTableRow(row))
2637
+ ...lines,
2638
+ `- ${layout.configuredPluginsLabel}:`,
2639
+ ...input.plugins.data.plugins.map((plugin) => ` - ${plugin}`)
2637
2640
  ];
2638
2641
  }
2639
- function buildMarkdownStatusTable(headers, rows) {
2642
+ function presentStatusMarkdownPluginLines(input, copy, layout) {
2643
+ if (input.plugins.status === "error") return presentStatusMarkdownErrorLines(input.plugins.error, copy, layout);
2644
+ const lines = [presentMarkdownStatusBullet(layout.configFileLabel, input.plugins.data.configFilePath, { codeValue: true })];
2645
+ if (input.plugins.data.plugins.length === 0) return [...lines, ...presentMarkdownEmptyStatusLines(layout.noPluginsMessage, layout)];
2640
2646
  return [
2641
- `| ${headers.map(sanitizeTableCell).join(" | ")} |`,
2642
- `| ${headers.map(() => "---").join(" | ")} |`,
2643
- ...rows.map((row) => `| ${row.map(sanitizeTableCell).join(" | ")} |`)
2647
+ ...lines,
2648
+ `- **${layout.configuredPluginsLabel}:**`,
2649
+ ...input.plugins.data.plugins.map((plugin) => ` - \`${plugin}\``)
2644
2650
  ];
2645
2651
  }
2646
- function formatPlainTableRow(columns) {
2647
- return `| ${columns.map(sanitizeTableCell).join(" | ")} |`;
2652
+ function presentStatusPlainLspLines(input, copy, layout) {
2653
+ if (input.lsp.status === "error") return presentStatusPlainErrorLines(input.lsp.error, copy, layout);
2654
+ if (input.lsp.data.statuses.length === 0) return presentPlainEmptyStatusLines(copy.lsp.none, layout);
2655
+ return input.lsp.data.statuses.flatMap((status) => presentPlainStatusGroup(status.name, [{
2656
+ label: layout.statusLabel,
2657
+ value: formatLspStatusBadge(status, copy)
2658
+ }, {
2659
+ codeValue: !!status.root,
2660
+ label: layout.rootLabel,
2661
+ value: status.root || "-"
2662
+ }]));
2648
2663
  }
2649
- function formatPlainTableSeparator(columnCount) {
2650
- return `| ${Array.from({ length: columnCount }, () => "---").join(" | ")} |`;
2664
+ function presentStatusMarkdownLspLines(input, copy, layout) {
2665
+ if (input.lsp.status === "error") return presentStatusMarkdownErrorLines(input.lsp.error, copy, layout);
2666
+ if (input.lsp.data.statuses.length === 0) return presentMarkdownEmptyStatusLines(copy.lsp.none, layout);
2667
+ return input.lsp.data.statuses.flatMap((status) => presentMarkdownStatusGroup(status.name, [{
2668
+ label: layout.statusLabel,
2669
+ value: formatLspStatusBadge(status, copy)
2670
+ }, {
2671
+ codeValue: !!status.root,
2672
+ label: layout.rootLabel,
2673
+ value: status.root || "-"
2674
+ }]));
2651
2675
  }
2652
- function sanitizeTableCell(value) {
2653
- return value.replace(/\r\n?/g, " / ").replace(/\|/g, "/").trim();
2676
+ function presentStatusPlainMcpLines(input, copy, layout) {
2677
+ if (input.mcp.status === "error") return presentStatusPlainErrorLines(input.mcp.error, copy, layout);
2678
+ if (input.mcp.data.statuses.length === 0) return presentPlainEmptyStatusLines(copy.mcp.none, layout);
2679
+ return input.mcp.data.statuses.flatMap(({ name, status }) => presentPlainStatusGroup(name, [{
2680
+ label: layout.statusLabel,
2681
+ value: formatMcpStatusBadge(status, copy, layout)
2682
+ }, {
2683
+ label: layout.mcpNotesLabel,
2684
+ value: formatMcpStatusNotes(status, layout)
2685
+ }]));
2686
+ }
2687
+ function presentStatusMarkdownMcpLines(input, copy, layout) {
2688
+ if (input.mcp.status === "error") return presentStatusMarkdownErrorLines(input.mcp.error, copy, layout);
2689
+ if (input.mcp.data.statuses.length === 0) return presentMarkdownEmptyStatusLines(copy.mcp.none, layout);
2690
+ return input.mcp.data.statuses.flatMap(({ name, status }) => presentMarkdownStatusGroup(name, [{
2691
+ label: layout.statusLabel,
2692
+ value: formatMcpStatusBadge(status, copy, layout)
2693
+ }, {
2694
+ label: layout.mcpNotesLabel,
2695
+ value: formatMcpStatusNotes(status, layout)
2696
+ }], { codeName: true }));
2654
2697
  }
2655
2698
  function presentStatusPlainErrorLines(error, copy, layout) {
2656
2699
  const detailLines = splitStatusLines(presentError(error, copy));
@@ -2660,14 +2703,23 @@ function presentStatusMarkdownErrorLines(error, copy, layout) {
2660
2703
  const detailLines = splitStatusLines(presentError(error, copy));
2661
2704
  return [presentMarkdownStatusBullet(layout.statusLabel, layout.errorStatus), ...detailLines.map((line) => presentMarkdownStatusBullet(layout.detailsLabel, line))];
2662
2705
  }
2663
- function flattenStatusError(error, copy) {
2664
- return splitStatusLines(presentError(error, copy)).join(" / ");
2706
+ function presentPlainEmptyStatusLines(message, layout) {
2707
+ return [presentPlainStatusBullet(layout.statusLabel, layout.noneStatus), presentPlainStatusBullet(layout.detailsLabel, message)];
2708
+ }
2709
+ function presentMarkdownEmptyStatusLines(message, layout) {
2710
+ return [presentMarkdownStatusBullet(layout.statusLabel, layout.noneStatus), presentMarkdownStatusBullet(layout.detailsLabel, message)];
2711
+ }
2712
+ function presentPlainStatusGroup(name, details) {
2713
+ return [`- ${normalizeStatusInlineValue(name)}`, ...details.map((detail) => ` - ${detail.label}: ${formatStatusValue(detail.value)}`)];
2714
+ }
2715
+ function presentMarkdownStatusGroup(name, details, options = {}) {
2716
+ return [`- ${options.codeName ? `\`${normalizeStatusInlineValue(name)}\`` : `**${normalizeStatusInlineValue(name)}**`}`, ...details.map((detail) => detail.codeValue ? ` - **${detail.label}:** \`${formatStatusValue(detail.value)}\`` : ` - **${detail.label}:** ${formatStatusValue(detail.value)}`)];
2665
2717
  }
2666
2718
  function presentPlainStatusBullet(label, value) {
2667
- return `- ${label}: ${value}`;
2719
+ return `- ${label}: ${formatStatusValue(value)}`;
2668
2720
  }
2669
2721
  function presentMarkdownStatusBullet(label, value, options = {}) {
2670
- return options.codeValue ? `- **${label}:** \`${value}\`` : `- **${label}:** ${value}`;
2722
+ return options.codeValue ? `- **${label}:** \`${formatStatusValue(value)}\`` : `- **${label}:** ${formatStatusValue(value)}`;
2671
2723
  }
2672
2724
  function splitStatusLines(text) {
2673
2725
  return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
@@ -2677,16 +2729,16 @@ function formatHealthBadge(healthy, layout) {
2677
2729
  }
2678
2730
  function formatLspStatusBadge(status, copy) {
2679
2731
  switch (status.status) {
2680
- case "connected": return `๐ŸŸข ${copy.lsp.connected}`;
2681
- case "error": return `๐Ÿ”ด ${copy.lsp.error}`;
2732
+ case "connected": return `\uD83D\uDFE2 ${copy.lsp.connected}`;
2733
+ case "error": return `\uD83D\uDD34 ${copy.lsp.error}`;
2682
2734
  }
2683
2735
  return status.status;
2684
2736
  }
2685
2737
  function formatMcpStatusBadge(status, copy, layout) {
2686
2738
  switch (status.status) {
2687
- case "connected": return `๐ŸŸข ${copy.mcp.connected}`;
2688
- case "disabled": return `โšช ${copy.mcp.disabled}`;
2689
- case "needs_auth": return `๐ŸŸก ${copy.mcp.needsAuth}`;
2739
+ case "connected": return `\uD83D\uDFE2 ${copy.mcp.connected}`;
2740
+ case "disabled": return `\u26AA ${copy.mcp.disabled}`;
2741
+ case "needs_auth": return `\uD83D\uDFE1 ${copy.mcp.needsAuth}`;
2690
2742
  case "failed": return layout.mcpFailedStatus;
2691
2743
  case "needs_client_registration": return layout.mcpRegistrationRequiredStatus;
2692
2744
  }
@@ -2705,49 +2757,60 @@ function formatMcpStatusNotes(status, layout) {
2705
2757
  function formatStatusDate(value) {
2706
2758
  return value.toISOString().slice(0, 10);
2707
2759
  }
2760
+ function formatStatusValue(value) {
2761
+ const normalized = value.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" / ");
2762
+ return normalized.length > 0 ? normalized : "-";
2763
+ }
2764
+ function normalizeStatusInlineValue(value) {
2765
+ return formatStatusValue(value);
2766
+ }
2708
2767
  function getStatusLayoutCopy(copy) {
2709
2768
  if (copy.systemStatus.title === BOT_COPY.systemStatus.title) return {
2769
+ configFileLabel: "Config File",
2770
+ configuredPluginsLabel: "Configured Plugins",
2710
2771
  currentDirectoryLabel: "Current Directory",
2711
2772
  detailsLabel: "Details",
2712
- divider: "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
2773
+ divider: "----------------",
2713
2774
  errorStatus: "๐Ÿ”ด Unhealthy",
2714
2775
  healthyStatus: "๐ŸŸข Healthy",
2715
- languageLabel: "Language",
2716
2776
  lastUpdatedLabel: "Last updated",
2717
2777
  lspTitle: "๐Ÿง  LSP (Language Server)",
2778
+ mcpFailedStatus: "๐Ÿ”ด Failed",
2718
2779
  mcpNotesLabel: "Notes",
2719
2780
  mcpRegistrationRequiredStatus: "๐ŸŸก Registration Required",
2720
2781
  mcpTitle: "๐Ÿ”Œ MCP (Model Context Protocol)",
2721
- mcpFailedStatus: "๐Ÿ”ด Failed",
2782
+ noPluginsMessage: "No plugins configured in the OpenCode config.",
2722
2783
  noneStatus: "โšช None",
2723
2784
  okLabel: "OK",
2724
2785
  pageTitle: "๐Ÿ“Š Service Status",
2786
+ pluginsTitle: "๐Ÿงฉ Plugins",
2725
2787
  rootLabel: "Root",
2726
2788
  serverTitle: "๐Ÿ–ฅ๏ธ Server",
2727
- serviceLabel: "Service",
2728
2789
  statusLabel: "Status",
2729
2790
  versionLabel: "Version",
2730
2791
  workspaceTitle: "๐Ÿ“ Workspace"
2731
2792
  };
2732
2793
  return {
2794
+ configFileLabel: "้…็ฝฎๆ–‡ไปถ",
2795
+ configuredPluginsLabel: "ๅทฒ้…็ฝฎๆ’ไปถ",
2733
2796
  currentDirectoryLabel: "ๅฝ“ๅ‰็›ฎๅฝ•",
2734
2797
  detailsLabel: "่ฏฆๆƒ…",
2735
- divider: "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
2798
+ divider: "----------------",
2736
2799
  errorStatus: "๐Ÿ”ด ๅผ‚ๅธธ",
2737
2800
  healthyStatus: "๐ŸŸข ๅฅๅบท",
2738
- languageLabel: "่ฏญ่จ€",
2739
2801
  lastUpdatedLabel: "ๆœ€ๅŽๆ›ดๆ–ฐ",
2740
2802
  lspTitle: "๐Ÿง  LSP (Language Server)",
2803
+ mcpFailedStatus: "๐Ÿ”ด ๅคฑ่ดฅ",
2741
2804
  mcpNotesLabel: "่ฏดๆ˜Ž",
2742
2805
  mcpRegistrationRequiredStatus: "๐ŸŸก ้œ€่ฆๆณจๅ†Œ",
2743
2806
  mcpTitle: "๐Ÿ”Œ MCP (Model Context Protocol)",
2744
- mcpFailedStatus: "๐Ÿ”ด ๅคฑ่ดฅ",
2807
+ noPluginsMessage: "ๅฝ“ๅ‰ OpenCode ้…็ฝฎไธญๆœช้…็ฝฎๆ’ไปถใ€‚",
2745
2808
  noneStatus: "โšช ๆ— ",
2746
2809
  okLabel: "ๆญฃๅธธ",
2747
2810
  pageTitle: "๐Ÿ“Š ๆœๅŠก็Šถๆ€",
2811
+ pluginsTitle: "๐Ÿงฉ ๆ’ไปถ",
2748
2812
  rootLabel: "ๆ น็›ฎๅฝ•",
2749
2813
  serverTitle: "๐Ÿ–ฅ๏ธ ๆœๅŠก็ซฏ",
2750
- serviceLabel: "ๆœๅŠก",
2751
2814
  statusLabel: "็Šถๆ€",
2752
2815
  versionLabel: "็‰ˆๆœฌ",
2753
2816
  workspaceTitle: "๐Ÿ“ ๅทฅไฝœๅŒบ"