dsh-cloudq 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 CloudQ 集成插件。插件为 Web Profile 提供 CloudQ 模式,内置 `cloudq` Skill,并提供 CloudQ 用量、架构视图、本地凭证配置和插件管理能力。
4
4
 
5
+ ## 截图
6
+
7
+ ![CloudQ 风险分析对话](assets/screenshots/chat-risk-analysis.png)
8
+
9
+ ![CloudQ 设置与插件管理](assets/screenshots/settings.png)
10
+
5
11
  ## 环境要求
6
12
 
7
13
  - Node.js `>=22.19.0`
@@ -35,7 +41,7 @@ dsh plugin --profile web remove dsh-cloudq
35
41
 
36
42
  ## 凭证配置
37
43
 
38
- 设置卡片支持 CloudQ OAuth 流程,也支持手动配置腾讯云 `SecretId` 和 `SecretKey`。
44
+ 设置卡片支持配置腾讯云 `SecretId` 和 `SecretKey` 凭证。
39
45
 
40
46
  - 凭证保存在本机 `~/.tencent-cloudq/credential.json`,文件权限仅允许当前用户访问。
41
47
  - 敏感凭证通过标准输入传递给 Python 辅助脚本,不会放入命令行参数。
@@ -84,6 +90,11 @@ cordis.patch.yml DSH Bundle 配置层
84
90
 
85
91
  源码在 [TencentCloud/cloudq-for-dsh](https://github.com/TencentCloud/cloudq-for-dsh) 完成评审和版本管理。只有仓库检查与安装包冒烟测试全部通过后,才可将 npm 版本发布到官方 Registry。
86
92
 
93
+ ## 更多文档
94
+
95
+ - 更新日志:[`CHANGELOG.md`](CHANGELOG.md)
96
+ - 开发与维护指南:[`DEVELOPMENT.md`](DEVELOPMENT.md)
97
+
87
98
  ## 许可证
88
99
 
89
100
  本项目采用 MIT 许可证,详情请参阅 [`LICENSE`](LICENSE)。
package/lib/client.js CHANGED
@@ -100,6 +100,11 @@ window.__ModuleLoader__.load({
100
100
  background: var(--dsw-alias-bg-layer-3, #fff);
101
101
  color: var(--dsw-alias-label-primary, #17191c);
102
102
  }
103
+ .dsh-cloudq-settings-card *,
104
+ .dsh-cloudq-settings-card *::before,
105
+ .dsh-cloudq-settings-card *::after {
106
+ box-sizing: border-box;
107
+ }
103
108
  .dsh-cloudq-settings-card__summary {
104
109
  padding: 15px 16px;
105
110
  }
@@ -215,10 +220,11 @@ window.__ModuleLoader__.load({
215
220
  flex-wrap: wrap;
216
221
  }
217
222
  .dsh-cloudq-settings-card__actions--stacked {
218
- flex-direction: column-reverse;
223
+ flex-direction: column;
224
+ align-items: stretch;
219
225
  }
220
226
  .dsh-cloudq-settings-card__actions--stacked button {
221
- width: 100%;
227
+ align-self: flex-end;
222
228
  }
223
229
  .dsh-cloudq-settings-card__btn {
224
230
  display: inline-flex;
@@ -426,10 +432,6 @@ window.__ModuleLoader__.load({
426
432
  font-size: 11px;
427
433
  font-weight: 500;
428
434
  }
429
- .dsh-cloudq-pm__row-tag--self {
430
- background: var(--dsw-alias-state-business-primary, #315efb);
431
- color: #fff;
432
- }
433
435
  .dsh-cloudq-pm__row-tag--off {
434
436
  background: var(--dsw-alias-state-danger, #d54941);
435
437
  color: #fff;
@@ -1137,12 +1139,145 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
1137
1139
  margin-bottom: 16px;
1138
1140
  }
1139
1141
  .dsh-cloudq-usage__card {
1140
- flex: 1;
1142
+ flex: 0 1 auto;
1143
+ min-width: 240px;
1141
1144
  padding: 14px 16px;
1142
1145
  border: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
1143
1146
  border-radius: 10px;
1144
1147
  background: var(--dsw-alias-bg-sub, #f7f8fa);
1145
1148
  }
1149
+ .dsh-cloudq-usage__card-value-row {
1150
+ display: flex;
1151
+ align-items: baseline;
1152
+ gap: 6px;
1153
+ flex-wrap: wrap;
1154
+ }
1155
+ .dsh-cloudq-usage__card-unit {
1156
+ font-size: 13px;
1157
+ color: var(--dsw-alias-label-secondary, #626a76);
1158
+ }
1159
+ .dsh-cloudq-usage__free-badge {
1160
+ position: relative;
1161
+ display: inline-flex;
1162
+ align-items: center;
1163
+ gap: 4px;
1164
+ padding: 1px 8px;
1165
+ border-radius: 999px;
1166
+ background: var(--dsw-alias-state-business-primary-bg, #e8efff);
1167
+ color: var(--dsw-alias-state-business-primary, #315efb);
1168
+ font-size: 12px;
1169
+ cursor: help;
1170
+ }
1171
+ .dsh-cloudq-usage__free-badge .dsh-cloudq-tooltip {
1172
+ opacity: 0;
1173
+ visibility: hidden;
1174
+ transition: opacity .12s ease;
1175
+ }
1176
+ .dsh-cloudq-usage__free-badge:hover .dsh-cloudq-tooltip,
1177
+ .dsh-cloudq-usage__free-badge:focus-visible .dsh-cloudq-tooltip {
1178
+ opacity: 1;
1179
+ visibility: visible;
1180
+ }
1181
+ .dsh-cloudq-usage__range {
1182
+ display: flex;
1183
+ align-items: center;
1184
+ gap: 8px;
1185
+ flex-wrap: wrap;
1186
+ margin-bottom: 12px;
1187
+ }
1188
+ .dsh-cloudq-usage__range-picker {
1189
+ position: relative;
1190
+ }
1191
+ .dsh-cloudq-usage__range-btn {
1192
+ display: inline-flex;
1193
+ align-items: center;
1194
+ gap: 8px;
1195
+ height: 28px;
1196
+ padding: 0 10px;
1197
+ border: 1px solid var(--dsw-alias-interactive-border, #cdd3da);
1198
+ border-radius: 6px;
1199
+ background: var(--dsw-alias-bg-card, #fff);
1200
+ color: var(--dsw-alias-label-primary, #1d2129);
1201
+ font-size: 12px;
1202
+ cursor: pointer;
1203
+ transition: border-color .15s ease, box-shadow .15s ease;
1204
+ }
1205
+ .dsh-cloudq-usage__range-btn:hover {
1206
+ border-color: var(--dsw-alias-state-business-primary, #315efb);
1207
+ }
1208
+ .dsh-cloudq-usage__range-picker.is-open .dsh-cloudq-usage__range-btn {
1209
+ border-color: var(--dsw-alias-state-business-primary, #315efb);
1210
+ box-shadow: 0 0 0 2px rgba(49, 94, 251, .12);
1211
+ }
1212
+ .dsh-cloudq-usage__range-btn-chevron {
1213
+ font-size: 10px;
1214
+ color: var(--dsw-alias-label-tertiary, #9aa1ab);
1215
+ transition: transform .15s ease;
1216
+ }
1217
+ .dsh-cloudq-usage__range-picker.is-open .dsh-cloudq-usage__range-btn-chevron {
1218
+ transform: rotate(180deg);
1219
+ }
1220
+ .dsh-cloudq-usage__range-menu {
1221
+ position: absolute;
1222
+ z-index: 1400;
1223
+ top: calc(100% + 6px);
1224
+ left: 0;
1225
+ min-width: 136px;
1226
+ padding: 4px;
1227
+ border: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
1228
+ border-radius: 8px;
1229
+ background: var(--dsw-alias-bg-card, #fff);
1230
+ box-shadow: 0 8px 24px rgba(29, 33, 41, .12);
1231
+ }
1232
+ .dsh-cloudq-usage__range-option {
1233
+ display: flex;
1234
+ align-items: center;
1235
+ gap: 4px;
1236
+ width: 100%;
1237
+ padding: 6px 8px;
1238
+ border: 0;
1239
+ border-radius: 5px;
1240
+ background: transparent;
1241
+ color: var(--dsw-alias-label-primary, #1d2129);
1242
+ font-size: 12px;
1243
+ text-align: left;
1244
+ cursor: pointer;
1245
+ }
1246
+ .dsh-cloudq-usage__range-option:hover {
1247
+ background: var(--dsw-alias-bg-sub, #f2f4f8);
1248
+ }
1249
+ .dsh-cloudq-usage__range-option.is-active {
1250
+ color: var(--dsw-alias-state-business-primary, #315efb);
1251
+ font-weight: 500;
1252
+ }
1253
+ .dsh-cloudq-usage__range-option-check {
1254
+ width: 14px;
1255
+ flex: none;
1256
+ color: var(--dsw-alias-state-business-primary, #315efb);
1257
+ visibility: hidden;
1258
+ }
1259
+ .dsh-cloudq-usage__range-option.is-active .dsh-cloudq-usage__range-option-check {
1260
+ visibility: visible;
1261
+ }
1262
+ .dsh-cloudq-usage__date {
1263
+ height: 28px;
1264
+ padding: 0 8px;
1265
+ border: 1px solid var(--dsw-alias-interactive-border, #cdd3da);
1266
+ border-radius: 6px;
1267
+ background: var(--dsw-alias-bg-card, #fff);
1268
+ color: var(--dsw-alias-label-primary, #1d2129);
1269
+ font-size: 12px;
1270
+ outline: none;
1271
+ transition: border-color .15s ease, box-shadow .15s ease;
1272
+ }
1273
+ .dsh-cloudq-usage__date:focus {
1274
+ border-color: var(--dsw-alias-state-business-primary, #315efb);
1275
+ box-shadow: 0 0 0 2px rgba(49, 94, 251, .12);
1276
+ }
1277
+ .dsh-cloudq-usage__range-error {
1278
+ font-size: 12px;
1279
+ color: var(--dsw-alias-state-danger, #d54941);
1280
+ }
1146
1281
  .dsh-cloudq-usage__card-label {
1147
1282
  font-size: 12px;
1148
1283
  color: var(--dsw-alias-label-tertiary, #9aa1ab);
@@ -1745,22 +1880,58 @@ display: none;
1745
1880
  const API_ARCH_DIRECTORIES = "/api/dsh-cloudq/architecture/directories";
1746
1881
  const API_ARCH_LIST = "/api/dsh-cloudq/architecture/list";
1747
1882
  const CLOUDQ_ARCH_FOLDER_KEY = "dsh-cloudq.architecture-folder-id";
1748
- function defaultUsageRange() {
1883
+ const USAGE_RANGE_PRESETS = [
1884
+ {
1885
+ id: "3d",
1886
+ label: "最近 3 天",
1887
+ days: 3
1888
+ },
1889
+ {
1890
+ id: "7d",
1891
+ label: "最近 1 周",
1892
+ days: 7
1893
+ },
1894
+ {
1895
+ id: "30d",
1896
+ label: "最近 1 月",
1897
+ days: 30
1898
+ },
1899
+ {
1900
+ id: "custom",
1901
+ label: "自定义",
1902
+ days: 0
1903
+ }
1904
+ ];
1905
+ let usagePreset = "7d";
1906
+ let usageCustomStart = "";
1907
+ let usageCustomEnd = "";
1908
+ function formatUsageTimestamp(d) {
1909
+ const pad = (n) => String(n).padStart(2, "0");
1910
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1911
+ }
1912
+ /** Resolve the current range to API timestamps; null when custom dates are incomplete. */
1913
+ function usageRangeTimestamps() {
1914
+ if (usagePreset === "custom") {
1915
+ if (!usageCustomStart || !usageCustomEnd) return null;
1916
+ return {
1917
+ start: `${usageCustomStart} 00:00:00`,
1918
+ end: `${usageCustomEnd} 23:59:59`
1919
+ };
1920
+ }
1921
+ const preset = USAGE_RANGE_PRESETS.find((item) => item.id === usagePreset) ?? USAGE_RANGE_PRESETS[1];
1749
1922
  const end = /* @__PURE__ */ new Date();
1750
- const start = /* @__PURE__ */ new Date(end.getTime() - 6048e5);
1751
- const fmt = (d) => {
1752
- const pad = (n) => String(n).padStart(2, "0");
1753
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1754
- };
1755
1923
  return {
1756
- start: fmt(start),
1757
- end: fmt(end)
1924
+ start: formatUsageTimestamp(/* @__PURE__ */ new Date(end.getTime() - preset.days * 24 * 60 * 60 * 1e3)),
1925
+ end: formatUsageTimestamp(end)
1758
1926
  };
1759
1927
  }
1760
1928
  function formatCredits(value) {
1761
1929
  const num = typeof value === "number" ? value : Number(value);
1762
1930
  if (!Number.isFinite(num)) return "—";
1763
- return num.toFixed(2);
1931
+ return num.toLocaleString("en-US", {
1932
+ minimumFractionDigits: 2,
1933
+ maximumFractionDigits: 2
1934
+ });
1764
1935
  }
1765
1936
  function formatDateTime(raw) {
1766
1937
  if (!raw) return "—";
@@ -1822,93 +1993,221 @@ display: none;
1822
1993
  let architecturePickerCleanup = null;
1823
1994
  function renderUsageView() {
1824
1995
  panelBody.textContent = "";
1825
- const range = defaultUsageRange();
1826
- const hint = document.createElement("div");
1827
- hint.className = "dsh-cloudq-panel__hint";
1828
- hint.textContent = "正在加载用量…";
1829
- panelBody.appendChild(hint);
1830
- cloudqRequest(`${API_USAGE}?start=${encodeURIComponent(range.start)}&end=${encodeURIComponent(range.end)}`).then((data) => {
1831
- if (panelView !== "usage" || !panelExpanded) return;
1832
- panelBody.textContent = "";
1833
- const overview = data.overview ?? {};
1834
- const rows = Array.isArray(data.detail) ? data.detail : [];
1835
- const overviewWrap = document.createElement("div");
1836
- overviewWrap.className = "dsh-cloudq-usage__overview";
1837
- const createUsageCard = (labelText, valueText, detailText) => {
1838
- const card = document.createElement("div");
1839
- card.className = "dsh-cloudq-usage__card";
1840
- const label = document.createElement("div");
1841
- label.className = "dsh-cloudq-usage__card-label";
1842
- label.textContent = labelText;
1843
- const value = document.createElement("div");
1844
- value.className = "dsh-cloudq-usage__card-value";
1845
- value.textContent = valueText;
1846
- const detail = document.createElement("div");
1847
- detail.className = "dsh-cloudq-usage__card-sub";
1848
- detail.textContent = detailText;
1849
- card.append(label, value, detail);
1850
- return card;
1851
- };
1852
- const cardTotal = createUsageCard("剩余积分", formatCredits(overview.TotalCredits), `自 ${overview.TotalStartDate ?? "—"} 起累计`);
1853
- const monthly = overview.MonthlyCredits;
1854
- const cardMonthly = createUsageCard("本月积分", monthly === null || monthly === void 0 ? "—" : formatCredits(monthly), overview.MonthlyStartDate ? `起于 ${overview.MonthlyStartDate}` : "暂无月度数据");
1855
- overviewWrap.append(cardTotal, cardMonthly);
1856
- const table = document.createElement("table");
1857
- table.className = "dsh-cloudq-usage__table";
1858
- const thead = document.createElement("thead");
1859
- const headerRow = document.createElement("tr");
1860
- for (const heading of [
1861
- "时间",
1862
- "渠道",
1863
- "内容",
1864
- "积分"
1865
- ]) {
1866
- const cell = document.createElement("th");
1867
- cell.textContent = heading;
1868
- headerRow.appendChild(cell);
1869
- }
1870
- thead.appendChild(headerRow);
1871
- table.appendChild(thead);
1872
- const tbody = document.createElement("tbody");
1873
- if (rows.length === 0) {
1874
- const tr = document.createElement("tr");
1875
- const empty = document.createElement("td");
1876
- empty.colSpan = 4;
1877
- empty.className = "dsh-cloudq-usage__table__empty";
1878
- empty.textContent = " 7 天暂无用量记录";
1879
- tr.appendChild(empty);
1880
- tbody.appendChild(tr);
1881
- } else for (const row of rows) {
1882
- const tr = document.createElement("tr");
1883
- const time = document.createElement("td");
1884
- time.textContent = formatDateTime(row.StartTime);
1885
- const channel = document.createElement("td");
1886
- channel.textContent = row.Channel ?? "—";
1887
- const message = document.createElement("td");
1888
- const msgSpan = document.createElement("span");
1889
- msgSpan.className = "dsh-cloudq-usage__table__message";
1890
- msgSpan.textContent = row.Message ?? "";
1891
- message.appendChild(msgSpan);
1892
- const credits = document.createElement("td");
1893
- credits.className = "dsh-cloudq-usage__table__credits";
1894
- credits.textContent = formatCredits(row.Credits);
1895
- tr.appendChild(time);
1896
- tr.appendChild(channel);
1897
- tr.appendChild(message);
1898
- tr.appendChild(credits);
1899
- tbody.appendChild(tr);
1996
+ const overviewWrap = document.createElement("div");
1997
+ overviewWrap.className = "dsh-cloudq-usage__overview";
1998
+ const card = document.createElement("div");
1999
+ card.className = "dsh-cloudq-usage__card";
2000
+ const label = document.createElement("div");
2001
+ label.className = "dsh-cloudq-usage__card-label";
2002
+ label.textContent = "累计消耗 Credit";
2003
+ const valueRow = document.createElement("div");
2004
+ valueRow.className = "dsh-cloudq-usage__card-value-row";
2005
+ const value = document.createElement("span");
2006
+ value.className = "dsh-cloudq-usage__card-value";
2007
+ value.textContent = "";
2008
+ const unit = document.createElement("span");
2009
+ unit.className = "dsh-cloudq-usage__card-unit";
2010
+ unit.textContent = "";
2011
+ const freeBadge = document.createElement("span");
2012
+ freeBadge.className = "dsh-cloudq-usage__free-badge";
2013
+ freeBadge.textContent = "公测期免费 ⓘ";
2014
+ freeBadge.tabIndex = 0;
2015
+ const freeBadgeTip = document.createElement("span");
2016
+ freeBadgeTip.className = "dsh-cloudq-tooltip";
2017
+ freeBadgeTip.setAttribute("role", "tooltip");
2018
+ freeBadgeTip.textContent = "正式计费将提前 1 个月通知";
2019
+ freeBadge.appendChild(freeBadgeTip);
2020
+ valueRow.append(value, unit, freeBadge);
2021
+ card.append(label, valueRow);
2022
+ overviewWrap.appendChild(card);
2023
+ panelBody.appendChild(overviewWrap);
2024
+ const rangeBar = document.createElement("div");
2025
+ rangeBar.className = "dsh-cloudq-usage__range";
2026
+ const picker = document.createElement("div");
2027
+ picker.className = "dsh-cloudq-usage__range-picker";
2028
+ const pickerBtn = document.createElement("button");
2029
+ pickerBtn.type = "button";
2030
+ pickerBtn.className = "dsh-cloudq-usage__range-btn";
2031
+ pickerBtn.setAttribute("aria-haspopup", "listbox");
2032
+ pickerBtn.setAttribute("aria-expanded", "false");
2033
+ pickerBtn.setAttribute("aria-label", "用量时间范围");
2034
+ const pickerLabel = document.createElement("span");
2035
+ pickerLabel.className = "dsh-cloudq-usage__range-btn-label";
2036
+ const pickerChevron = document.createElement("span");
2037
+ pickerChevron.className = "dsh-cloudq-usage__range-btn-chevron";
2038
+ pickerChevron.textContent = "▾";
2039
+ pickerBtn.append(pickerLabel, pickerChevron);
2040
+ const menu = document.createElement("div");
2041
+ menu.className = "dsh-cloudq-usage__range-menu";
2042
+ menu.setAttribute("role", "listbox");
2043
+ menu.hidden = true;
2044
+ const optionButtons = /* @__PURE__ */ new Map();
2045
+ for (const preset of USAGE_RANGE_PRESETS) {
2046
+ const option = document.createElement("button");
2047
+ option.type = "button";
2048
+ option.className = "dsh-cloudq-usage__range-option";
2049
+ option.setAttribute("role", "option");
2050
+ const check = document.createElement("span");
2051
+ check.className = "dsh-cloudq-usage__range-option-check";
2052
+ check.textContent = "✓";
2053
+ const text = document.createElement("span");
2054
+ text.textContent = preset.label;
2055
+ option.append(check, text);
2056
+ option.addEventListener("click", () => {
2057
+ usagePreset = preset.id;
2058
+ syncPicker();
2059
+ closeMenu();
2060
+ syncCustomVisibility();
2061
+ loadUsage();
2062
+ });
2063
+ optionButtons.set(preset.id, option);
2064
+ menu.appendChild(option);
2065
+ }
2066
+ const syncPicker = () => {
2067
+ const active = USAGE_RANGE_PRESETS.find((item) => item.id === usagePreset) ?? USAGE_RANGE_PRESETS[1];
2068
+ pickerLabel.textContent = active.label;
2069
+ for (const [id, option] of optionButtons) {
2070
+ const isActive = id === active.id;
2071
+ option.classList.toggle("is-active", isActive);
2072
+ option.setAttribute("aria-selected", String(isActive));
1900
2073
  }
1901
- table.appendChild(tbody);
1902
- panelBody.appendChild(overviewWrap);
1903
- panelBody.appendChild(table);
1904
- }).catch((error) => {
1905
- if (!panelExpanded) return;
1906
- panelBody.textContent = "";
1907
- const err = document.createElement("div");
1908
- err.className = "dsh-cloudq-panel__error";
1909
- err.textContent = `用量加载失败:${error.message}`;
1910
- panelBody.appendChild(err);
2074
+ };
2075
+ const pickerAbort = new AbortController();
2076
+ const closeMenu = () => {
2077
+ menu.hidden = true;
2078
+ picker.classList.remove("is-open");
2079
+ pickerBtn.setAttribute("aria-expanded", "false");
2080
+ };
2081
+ pickerBtn.addEventListener("click", () => {
2082
+ const willOpen = menu.hidden;
2083
+ menu.hidden = !willOpen;
2084
+ picker.classList.toggle("is-open", willOpen);
2085
+ pickerBtn.setAttribute("aria-expanded", String(willOpen));
1911
2086
  });
2087
+ document.addEventListener("mousedown", (event) => {
2088
+ if (!menu.isConnected) {
2089
+ pickerAbort.abort();
2090
+ return;
2091
+ }
2092
+ if (!picker.contains(event.target)) closeMenu();
2093
+ }, { signal: pickerAbort.signal });
2094
+ document.addEventListener("keydown", (event) => {
2095
+ if (!menu.isConnected) return;
2096
+ if (event.key === "Escape") closeMenu();
2097
+ }, { signal: pickerAbort.signal });
2098
+ syncPicker();
2099
+ const startDate = document.createElement("input");
2100
+ startDate.type = "date";
2101
+ startDate.className = "dsh-cloudq-usage__date";
2102
+ startDate.setAttribute("aria-label", "开始日期");
2103
+ startDate.value = usageCustomStart;
2104
+ const endDate = document.createElement("input");
2105
+ endDate.type = "date";
2106
+ endDate.className = "dsh-cloudq-usage__date";
2107
+ endDate.setAttribute("aria-label", "结束日期");
2108
+ endDate.value = usageCustomEnd;
2109
+ const rangeError = document.createElement("span");
2110
+ rangeError.className = "dsh-cloudq-usage__range-error";
2111
+ const syncCustomVisibility = () => {
2112
+ const custom = usagePreset === "custom";
2113
+ startDate.hidden = !custom;
2114
+ endDate.hidden = !custom;
2115
+ };
2116
+ const tableWrap = document.createElement("div");
2117
+ const loadUsage = () => {
2118
+ const range = usageRangeTimestamps();
2119
+ rangeError.textContent = "";
2120
+ if (!range) {
2121
+ tableWrap.textContent = "";
2122
+ const hint = document.createElement("div");
2123
+ hint.className = "dsh-cloudq-panel__hint";
2124
+ hint.textContent = "请选择开始日期与结束日期。";
2125
+ tableWrap.appendChild(hint);
2126
+ return;
2127
+ }
2128
+ if (usagePreset === "custom" && usageCustomStart > usageCustomEnd) {
2129
+ rangeError.textContent = "开始日期不能晚于结束日期。";
2130
+ return;
2131
+ }
2132
+ tableWrap.textContent = "";
2133
+ const hint = document.createElement("div");
2134
+ hint.className = "dsh-cloudq-panel__hint";
2135
+ hint.textContent = "正在加载用量…";
2136
+ tableWrap.appendChild(hint);
2137
+ cloudqRequest(`${API_USAGE}?start=${encodeURIComponent(range.start)}&end=${encodeURIComponent(range.end)}`).then((data) => {
2138
+ if (panelView !== "usage" || !panelExpanded) return;
2139
+ value.textContent = formatCredits(data.overview?.TotalCredits);
2140
+ const rows = Array.isArray(data.detail) ? data.detail : [];
2141
+ tableWrap.textContent = "";
2142
+ const table = document.createElement("table");
2143
+ table.className = "dsh-cloudq-usage__table";
2144
+ const thead = document.createElement("thead");
2145
+ const headerRow = document.createElement("tr");
2146
+ for (const heading of [
2147
+ "时间",
2148
+ "渠道",
2149
+ "内容",
2150
+ "积分"
2151
+ ]) {
2152
+ const cell = document.createElement("th");
2153
+ cell.textContent = heading;
2154
+ headerRow.appendChild(cell);
2155
+ }
2156
+ thead.appendChild(headerRow);
2157
+ table.appendChild(thead);
2158
+ const tbody = document.createElement("tbody");
2159
+ if (rows.length === 0) {
2160
+ const tr = document.createElement("tr");
2161
+ const empty = document.createElement("td");
2162
+ empty.colSpan = 4;
2163
+ empty.className = "dsh-cloudq-usage__table__empty";
2164
+ empty.textContent = "当前时间范围内暂无用量记录";
2165
+ tr.appendChild(empty);
2166
+ tbody.appendChild(tr);
2167
+ } else for (const row of rows) {
2168
+ const tr = document.createElement("tr");
2169
+ const time = document.createElement("td");
2170
+ time.textContent = formatDateTime(row.StartTime);
2171
+ const channel = document.createElement("td");
2172
+ channel.textContent = row.Channel ?? "—";
2173
+ const message = document.createElement("td");
2174
+ const msgSpan = document.createElement("span");
2175
+ msgSpan.className = "dsh-cloudq-usage__table__message";
2176
+ msgSpan.textContent = row.Message ?? "";
2177
+ message.appendChild(msgSpan);
2178
+ const credits = document.createElement("td");
2179
+ credits.className = "dsh-cloudq-usage__table__credits";
2180
+ credits.textContent = formatCredits(row.Credits);
2181
+ tr.appendChild(time);
2182
+ tr.appendChild(channel);
2183
+ tr.appendChild(message);
2184
+ tr.appendChild(credits);
2185
+ tbody.appendChild(tr);
2186
+ }
2187
+ table.appendChild(tbody);
2188
+ tableWrap.appendChild(table);
2189
+ }).catch((error) => {
2190
+ if (panelView !== "usage" || !panelExpanded) return;
2191
+ tableWrap.textContent = "";
2192
+ const err = document.createElement("div");
2193
+ err.className = "dsh-cloudq-panel__error";
2194
+ err.textContent = cloudqPanelErrorText(error, "用量加载失败");
2195
+ tableWrap.appendChild(err);
2196
+ });
2197
+ };
2198
+ const onCustomDateChange = () => {
2199
+ usageCustomStart = startDate.value;
2200
+ usageCustomEnd = endDate.value;
2201
+ loadUsage();
2202
+ };
2203
+ startDate.addEventListener("change", onCustomDateChange);
2204
+ endDate.addEventListener("change", onCustomDateChange);
2205
+ picker.append(pickerBtn, menu);
2206
+ rangeBar.append(picker, startDate, endDate, rangeError);
2207
+ syncCustomVisibility();
2208
+ panelBody.appendChild(rangeBar);
2209
+ panelBody.appendChild(tableWrap);
2210
+ loadUsage();
1912
2211
  }
1913
2212
  const INSPIRATION_TABS = [
1914
2213
  {
@@ -2009,7 +2308,7 @@ display: none;
2009
2308
  list.textContent = "";
2010
2309
  const err = document.createElement("div");
2011
2310
  err.className = "dsh-cloudq-panel__error";
2012
- err.textContent = `灵感加载失败:${error instanceof Error ? error.message : "未知错误"}`;
2311
+ err.textContent = cloudqPanelErrorText(error, "灵感加载失败");
2013
2312
  list.appendChild(err);
2014
2313
  });
2015
2314
  }
@@ -2152,7 +2451,7 @@ display: none;
2152
2451
  panelBody.textContent = "";
2153
2452
  const err = document.createElement("div");
2154
2453
  err.className = "dsh-cloudq-panel__error";
2155
- err.textContent = `制品加载失败:${error instanceof Error ? error.message : "未知错误"}`;
2454
+ err.textContent = cloudqPanelErrorText(error, "制品加载失败");
2156
2455
  panelBody.appendChild(err);
2157
2456
  });
2158
2457
  }
@@ -2433,7 +2732,7 @@ display: none;
2433
2732
  summary.textContent = "加载失败";
2434
2733
  const err = document.createElement("div");
2435
2734
  err.className = "dsh-cloudq-panel__error";
2436
- err.textContent = `架构图加载失败:${error instanceof Error ? error.message : "未知错误"}`;
2735
+ err.textContent = cloudqPanelErrorText(error, "架构图加载失败");
2437
2736
  grid.appendChild(err);
2438
2737
  });
2439
2738
  };
@@ -2452,7 +2751,7 @@ display: none;
2452
2751
  panelBody.textContent = "";
2453
2752
  const err = document.createElement("div");
2454
2753
  err.className = "dsh-cloudq-panel__error";
2455
- err.textContent = `架构目录加载失败:${error instanceof Error ? error.message : "未知错误"}`;
2754
+ err.textContent = cloudqPanelErrorText(error, "架构目录加载失败");
2456
2755
  panelBody.appendChild(err);
2457
2756
  });
2458
2757
  }
@@ -2732,8 +3031,6 @@ display: none;
2732
3031
  };
2733
3032
  }
2734
3033
  const API_CREDENTIAL = "/api/dsh-cloudq/credential";
2735
- const API_LOGIN_URL = "/api/dsh-cloudq/login/url";
2736
- const API_LOGIN_SAVE = "/api/dsh-cloudq/login/save";
2737
3034
  const API_LOGOUT = "/api/dsh-cloudq/logout";
2738
3035
  const API_CREDENTIAL_TEST = "/api/dsh-cloudq/credential/test";
2739
3036
  const API_CREDENTIAL_SAVE = "/api/dsh-cloudq/credential/save";
@@ -2744,6 +3041,14 @@ display: none;
2744
3041
  this.code = code;
2745
3042
  }
2746
3043
  };
3044
+ /**
3045
+ * Panel-facing error text. A missing credential gets an actionable pointer to
3046
+ * the settings card instead of a raw load failure message.
3047
+ */
3048
+ function cloudqPanelErrorText(error, prefix) {
3049
+ if (error instanceof CloudQApiError && error.code === "NeedAuth") return "尚未配置 AK/SK,请前往「设置 → 插件 → CloudQ」完成配置后重试。";
3050
+ return `${prefix}:${error instanceof Error ? error.message : "未知错误"}`;
3051
+ }
2747
3052
  async function cloudqRequest(url, init, timeoutMs = 2e4) {
2748
3053
  const controller = new AbortController();
2749
3054
  const timeout = window.setTimeout(() => controller.abort(), timeoutMs);
@@ -2788,9 +3093,8 @@ display: none;
2788
3093
  const [loading, setLoading] = react.useState(true);
2789
3094
  const [busy, setBusy] = react.useState();
2790
3095
  const [status, setStatus] = react.useState();
2791
- const [authorizeUrl, setAuthorizeUrl] = react.useState("");
2792
- const [code, setCode] = react.useState("");
2793
3096
  const [feedback, setFeedback] = react.useState();
3097
+ const [validated, setValidated] = react.useState(false);
2794
3098
  const [secretId, setSecretId] = react.useState("");
2795
3099
  const [secretKey, setSecretKey] = react.useState("");
2796
3100
  const refresh = react.useCallback(async () => {
@@ -2810,61 +3114,13 @@ display: none;
2810
3114
  react.useEffect(() => {
2811
3115
  refresh();
2812
3116
  }, [refresh]);
2813
- const handleGetAuthorizeUrl = async () => {
2814
- setBusy("url");
2815
- setFeedback(void 0);
2816
- try {
2817
- const response = await cloudqRequest(API_LOGIN_URL, { method: "POST" });
2818
- setAuthorizeUrl(response.authorize_url);
2819
- if (response.authorize_url) window.open(response.authorize_url, "_blank", "noopener");
2820
- } catch (error) {
2821
- setFeedback({
2822
- kind: "error",
2823
- text: error.message
2824
- });
2825
- } finally {
2826
- setBusy(void 0);
2827
- }
2828
- };
2829
- const handleSaveCode = async () => {
2830
- const trimmed = code.trim();
2831
- if (!trimmed) {
2832
- setFeedback({
2833
- kind: "error",
2834
- text: "请先在浏览器中完成授权,然后粘贴授权码。"
2835
- });
2836
- return;
2837
- }
2838
- setBusy("save");
2839
- setFeedback(void 0);
2840
- try {
2841
- const response = await cloudqRequest(API_LOGIN_SAVE, {
2842
- method: "POST",
2843
- body: JSON.stringify({ code: trimmed })
2844
- });
2845
- setStatus(response.status);
2846
- setCode("");
2847
- setFeedback({
2848
- kind: "success",
2849
- text: "登录成功,凭证已保存。"
2850
- });
2851
- } catch (error) {
2852
- setFeedback({
2853
- kind: "error",
2854
- text: error.message
2855
- });
2856
- } finally {
2857
- setBusy(void 0);
2858
- }
2859
- };
2860
3117
  const handleLogout = async () => {
2861
3118
  setBusy("logout");
2862
3119
  setFeedback(void 0);
2863
3120
  try {
2864
3121
  await cloudqRequest(API_LOGOUT, { method: "POST" });
2865
3122
  setStatus({ logged_in: false });
2866
- setAuthorizeUrl("");
2867
- setCode("");
3123
+ setValidated(false);
2868
3124
  setFeedback({
2869
3125
  kind: "success",
2870
3126
  text: "已退出登录。"
@@ -2903,26 +3159,27 @@ display: none;
2903
3159
  setSecretId("");
2904
3160
  setSecretKey("");
2905
3161
  }
3162
+ setValidated(true);
2906
3163
  setFeedback({
2907
3164
  kind: "success",
2908
3165
  text: successText
2909
3166
  });
2910
3167
  } catch (error) {
3168
+ setValidated(false);
3169
+ const invalid = error instanceof CloudQApiError && error.code !== "network-error" && error.code !== "invalid-response";
2911
3170
  setFeedback({
2912
3171
  kind: "error",
2913
- text: error.message
3172
+ text: invalid ? "AKSK 无效,请检查后重新配置。" : error.message
2914
3173
  });
2915
3174
  } finally {
2916
3175
  setBusy(void 0);
2917
3176
  }
2918
3177
  };
2919
- const handleTestConnection = () => submitAccessKey(API_CREDENTIAL_TEST, "test", "连接成功,该密钥可用于 CloudQ。");
2920
- const handleSaveAccessKey = () => submitAccessKey(API_CREDENTIAL_SAVE, "save-ak", "配置已保存,密钥已安全存放在 DSH Host 端。");
3178
+ const handleTestConnection = () => submitAccessKey(API_CREDENTIAL_TEST, "test", "连接成功,AKSK 有效。");
3179
+ const handleSaveAccessKey = () => submitAccessKey(API_CREDENTIAL_SAVE, "save-ak", "配置已保存,AKSK 有效。");
2921
3180
  const loggedIn = status?.logged_in === true;
2922
- const expired = status?.expired === true;
2923
- const badgeClass = expired ? "dsh-cloudq-settings-card__badge dsh-cloudq-settings-card__badge--warn" : loggedIn ? "dsh-cloudq-settings-card__badge dsh-cloudq-settings-card__badge--ok" : "dsh-cloudq-settings-card__badge dsh-cloudq-settings-card__badge--off";
2924
- const badgeText = expired ? "凭证已过期" : loggedIn ? "已登录" : "未登录";
2925
- const summarySubtitle = loggedIn ? "腾讯云 CloudQ 凭证已激活,可直接使用 CloudQ 模式。" : "用于在 CloudQ 模式下调用腾讯云 SSE API 与 CLI 工具。";
3181
+ const akskValid = validated || loggedIn;
3182
+ const summarySubtitle = akskValid ? "腾讯云 CloudQ 凭证已激活,可直接使用 CloudQ 模式。" : "用于在 CloudQ 模式下调用腾讯云 SSE API CLI 工具。";
2926
3183
  const disabled = loading || busy !== void 0;
2927
3184
  return react_jsx_runtime.jsx("section", {
2928
3185
  className: "dsh-cloudq-settings-card",
@@ -2938,9 +3195,9 @@ display: none;
2938
3195
  children: [react_jsx_runtime.jsx("strong", { children: "CloudQ" }), react_jsx_runtime.jsx("small", { children: summarySubtitle })]
2939
3196
  }), react_jsx_runtime.jsx("span", {
2940
3197
  className: "dsh-cloudq-settings-card__summary-right",
2941
- children: [react_jsx_runtime.jsx("span", {
2942
- className: badgeClass,
2943
- children: [react_jsx_runtime.jsx("span", { className: "dsh-cloudq-settings-card__badge-dot" }), badgeText]
3198
+ children: [akskValid && react_jsx_runtime.jsx("span", {
3199
+ className: "dsh-cloudq-settings-card__badge dsh-cloudq-settings-card__badge--ok",
3200
+ children: [react_jsx_runtime.jsx("span", { className: "dsh-cloudq-settings-card__badge-dot" }), "AKSK有效"]
2944
3201
  }), react_jsx_runtime.jsx("span", {
2945
3202
  className: `dsh-cloudq-settings-card__chevron${expanded ? " is-open" : ""}`,
2946
3203
  children: "▾"
@@ -2952,20 +3209,10 @@ display: none;
2952
3209
  children: [
2953
3210
  loading ? react_jsx_runtime.jsx("div", {
2954
3211
  className: "dsh-cloudq-settings-card__hint",
2955
- children: "正在检查 CloudQ 登录状态…"
3212
+ children: "正在检查凭证状态…"
2956
3213
  }) : react_jsx_runtime.jsx("div", {
2957
3214
  className: "dsh-cloudq-settings-card__rows",
2958
3215
  children: [
2959
- react_jsx_runtime.jsx("div", {
2960
- className: "dsh-cloudq-settings-card__row",
2961
- children: [react_jsx_runtime.jsx("span", {
2962
- className: "dsh-cloudq-settings-card__row-key",
2963
- children: "状态"
2964
- }), react_jsx_runtime.jsx("span", {
2965
- className: "dsh-cloudq-settings-card__row-val",
2966
- children: badgeText
2967
- })]
2968
- }),
2969
3216
  status?.credential_file ? react_jsx_runtime.jsx("div", {
2970
3217
  className: "dsh-cloudq-settings-card__row",
2971
3218
  children: [react_jsx_runtime.jsx("span", {
@@ -3023,35 +3270,6 @@ display: none;
3023
3270
  children: feedback.text
3024
3271
  }) : null,
3025
3272
  !loggedIn && react_jsx_runtime.jsx("div", { children: [
3026
- react_jsx_runtime.jsx("div", {
3027
- className: "dsh-cloudq-settings-card__actions",
3028
- children: [react_jsx_runtime.jsx("button", {
3029
- type: "button",
3030
- className: "dsh-cloudq-settings-card__btn dsh-cloudq-settings-card__btn--primary",
3031
- disabled,
3032
- onClick: handleGetAuthorizeUrl,
3033
- children: busy === "url" ? "获取中…" : "获取授权链接"
3034
- })]
3035
- }),
3036
- authorizeUrl && react_jsx_runtime.jsx("div", {
3037
- className: "dsh-cloudq-settings-card__hint",
3038
- children: "已在新标签页打开授权页。授权完成后,请将页面显示的授权码粘贴到下方:"
3039
- }),
3040
- react_jsx_runtime.jsx("div", {
3041
- className: "dsh-cloudq-settings-card__actions dsh-cloudq-settings-card__actions--stacked",
3042
- children: [react_jsx_runtime.jsx("input", {
3043
- className: "dsh-cloudq-settings-card__input",
3044
- placeholder: "粘贴授权码",
3045
- value: code,
3046
- onChange: (event) => setCode(event.target.value)
3047
- }), react_jsx_runtime.jsx("button", {
3048
- type: "button",
3049
- className: "dsh-cloudq-settings-card__btn",
3050
- disabled,
3051
- onClick: handleSaveCode,
3052
- children: busy === "save" ? "登录中…" : "完成登录"
3053
- })]
3054
- }),
3055
3273
  react_jsx_runtime.jsx("div", {
3056
3274
  className: "dsh-cloudq-settings-card__field",
3057
3275
  children: [
@@ -3281,17 +3499,10 @@ display: none;
3281
3499
  className: "dsh-cloudq-pm__row-info",
3282
3500
  children: [react_jsx_runtime.jsx("div", {
3283
3501
  className: "dsh-cloudq-pm__row-name",
3284
- children: [
3285
- plugin.name,
3286
- plugin.self ? react_jsx_runtime.jsx("span", {
3287
- className: "dsh-cloudq-pm__row-tag dsh-cloudq-pm__row-tag--self",
3288
- children: "本插件"
3289
- }) : null,
3290
- !on ? react_jsx_runtime.jsx("span", {
3291
- className: "dsh-cloudq-pm__row-tag dsh-cloudq-pm__row-tag--off",
3292
- children: "已停用"
3293
- }) : null
3294
- ]
3502
+ children: [plugin.name, !on ? react_jsx_runtime.jsx("span", {
3503
+ className: "dsh-cloudq-pm__row-tag dsh-cloudq-pm__row-tag--off",
3504
+ children: "已停用"
3505
+ }) : null]
3295
3506
  }), react_jsx_runtime.jsx("div", {
3296
3507
  className: "dsh-cloudq-pm__row-bundle",
3297
3508
  children: `${plugin.id} · ${plugin.bundle}`
package/lib/index.js CHANGED
@@ -392,9 +392,9 @@ function runScript$1(scriptsDirectory, scriptName, args, { timeoutMs = 3e4, json
392
392
  * and referenced through a directory `resourceBase`, so every `{baseDir}`
393
393
  * placeholder in the body resolves against the installed package directory.
394
394
  *
395
- * It also exposes a small credential-management API (status / authorize-url /
396
- * save-code / logout) that the client half renders in the settings panel, so
397
- * the CloudQ login flow no longer requires touching a terminal.
395
+ * It also exposes a small credential-management API (status / AK-SK test &
396
+ * save / logout) that the client half renders in the settings panel, so the
397
+ * CloudQ credential flow no longer requires touching a terminal.
398
398
  *
399
399
  * @module dsh-cloudq
400
400
  */
@@ -420,8 +420,13 @@ function renderedSkillContent() {
420
420
  return rawSkillContent().replace(/\{baseDir\}/g, baseDir);
421
421
  }
422
422
  /** Run one helper from the bundled CloudQ skill. */
423
- function runScript(scriptName, args, options) {
424
- return runScript$1(resolve(skillDirectory(), "scripts"), scriptName, args, options);
423
+ async function runScript(scriptName, args, options) {
424
+ try {
425
+ return await runScript$1(resolve(skillDirectory(), "scripts"), scriptName, args, options);
426
+ } catch (error) {
427
+ if (error instanceof HttpError && error.code === "NeedAuth") throw new HttpError(401, "NeedAuth", "尚未配置 AK/SK,请前往「设置 → 插件 → CloudQ」完成配置。");
428
+ throw error;
429
+ }
425
430
  }
426
431
  /** Remove host filesystem details before returning credential state to the browser. */
427
432
  function publicCredentialStatus(status) {
@@ -434,15 +439,6 @@ function publicCredentialStatus(status) {
434
439
  async function credentialStatus() {
435
440
  return publicCredentialStatus(await runScript("login.py", ["--status"]));
436
441
  }
437
- function authorizeUrl() {
438
- return runScript("login.py", ["--authorize-url"]);
439
- }
440
- async function saveAuthCode(code) {
441
- return publicCredentialStatus(await runScript("login.py", ["--save", "--stdin"], {
442
- stdin: JSON.stringify({ code }),
443
- sensitiveValues: [code]
444
- }));
445
- }
446
442
  function logout() {
447
443
  return runScript("logout.py", [], { jsonOnly: false });
448
444
  }
@@ -616,23 +612,29 @@ const inject = [
616
612
  "sessionQuery"
617
613
  ];
618
614
  function apply(ctx) {
619
- ctx.effect(() => ctx.settings.register(SETTINGS_NAMESPACE, Config, { base: {} }), "dsh-cloudq: settings namespace");
620
- ctx.effect(() => ctx.settings.register(PLUGIN_MANAGER_NAMESPACE, Config, { base: {} }), "dsh-cloudq: plugin-manager settings namespace");
621
- ctx.effect(() => ctx.skills.register({
622
- name: "cloudq",
623
- description: "用户咨询腾讯云产品资源、AWS、阿里云等多云资源时,查看智能顾问架构图、架构目录、架构详情、架构评估结果、绘制架构图、开通智能顾问时、AI智能巡检、AI容量监测、AI混沌演练、AI云诊断、主动预警、架构健康度、云运维问答、云资源查询、云成本优化、安全合规、云资源盘点、闲置资源检查、云产品最佳实践等AIOps、ChatOps、CloudOps操作时使用。",
624
- whenToUse: "用户要求进入 CloudQ 模式、使用 CloudQ、咨询云上架构/资源/成本/巡检/诊断等多云或腾讯云运维问题,或点击输入栏 CloudQ 按钮时使用;也适用于架构图查看/评估、AI 巡检、容量监测、混沌演练、云诊断等场景。",
625
- source: "bundled",
626
- resourceBase: {
627
- kind: "directory",
628
- path: skillDirectory()
629
- },
630
- content: renderedSkillContent(),
631
- invocation: {
632
- modelInvocable: true,
633
- userInvocable: true
634
- }
635
- }), "dsh-cloudq: CloudQ skill");
615
+ ctx.effect(() => {
616
+ ctx.settings.register(SETTINGS_NAMESPACE, Config, { base: {} });
617
+ }, "dsh-cloudq: settings namespace");
618
+ ctx.effect(() => {
619
+ ctx.settings.register(PLUGIN_MANAGER_NAMESPACE, Config, { base: {} });
620
+ }, "dsh-cloudq: plugin-manager settings namespace");
621
+ ctx.effect(() => {
622
+ ctx.skills.register({
623
+ name: "cloudq",
624
+ description: "用户咨询腾讯云产品资源、AWS、阿里云等多云资源时,查看智能顾问架构图、架构目录、架构详情、架构评估结果、绘制架构图、开通智能顾问时、AI智能巡检、AI容量监测、AI混沌演练、AI云诊断、主动预警、架构健康度、云运维问答、云资源查询、云成本优化、安全合规、云资源盘点、闲置资源检查、云产品最佳实践等AIOps、ChatOps、CloudOps操作时使用。",
625
+ whenToUse: "用户要求进入 CloudQ 模式、使用 CloudQ、咨询云上架构/资源/成本/巡检/诊断等多云或腾讯云运维问题,或点击输入栏 CloudQ 按钮时使用;也适用于架构图查看/评估、AI 巡检、容量监测、混沌演练、云诊断等场景。",
626
+ source: "bundled",
627
+ resourceBase: {
628
+ kind: "directory",
629
+ path: skillDirectory()
630
+ },
631
+ content: renderedSkillContent(),
632
+ invocation: {
633
+ modelInvocable: true,
634
+ userInvocable: true
635
+ }
636
+ });
637
+ }, "dsh-cloudq: CloudQ skill");
636
638
  ctx.effect(() => {
637
639
  const disposers = [];
638
640
  disposers.push(ctx.webServer.register({
@@ -650,41 +652,6 @@ function apply(ctx) {
650
652
  }
651
653
  }
652
654
  }));
653
- disposers.push(ctx.webServer.register({
654
- kind: "exact",
655
- path: "/api/dsh-cloudq/login/url",
656
- handler: async (request, response) => {
657
- try {
658
- assertSafeRequest(request, "POST");
659
- const result = await authorizeUrl();
660
- sendJson(response, 200, {
661
- ok: true,
662
- authorize_url: result.authorize_url,
663
- state: result.state
664
- });
665
- } catch (error) {
666
- sendError(response, error);
667
- }
668
- }
669
- }));
670
- disposers.push(ctx.webServer.register({
671
- kind: "exact",
672
- path: "/api/dsh-cloudq/login/save",
673
- handler: async (request, response) => {
674
- try {
675
- assertSafeRequest(request, "POST");
676
- const body = await readJsonBody(request);
677
- const code = typeof body?.code === "string" ? body.code.trim() : "";
678
- if (!code) throw new HttpError(400, "missing-code", "缺少授权码。");
679
- sendJson(response, 200, {
680
- ok: true,
681
- status: await saveAuthCode(code)
682
- });
683
- } catch (error) {
684
- sendError(response, error);
685
- }
686
- }
687
- }));
688
655
  disposers.push(ctx.webServer.register({
689
656
  kind: "exact",
690
657
  path: "/api/dsh-cloudq/logout",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cloudq",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CloudQ integration for DeepSeek Harness with secure credential, workspace, and plugin-management surfaces",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -139,7 +139,7 @@ def cmd_status() -> int:
139
139
  if cred_data is None:
140
140
  print(_json_ok({
141
141
  "logged_in": False,
142
- "message": "未找到 OAuth 凭证",
142
+ "message": "未找到凭证",
143
143
  }))
144
144
  return 0
145
145