dsh-claude-desktop-theme 0.4.4 → 0.4.6

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.
Files changed (59) hide show
  1. package/README.md +89 -69
  2. package/lib/client.js +18357 -7848
  3. package/lib/index.js +251 -14
  4. package/lib/types/client/compat/account-balance.d.ts +8 -0
  5. package/lib/types/client/compat/browser-header.d.ts +1 -2
  6. package/lib/types/client/compat/changed-files.d.ts +7 -0
  7. package/lib/types/client/compat/composer-status-order.d.ts +1 -54
  8. package/lib/types/client/compat/hero-greeting.d.ts +6 -0
  9. package/lib/types/client/compat/host-dom.d.ts +16 -0
  10. package/lib/types/client/compat/mutation-scope.d.ts +18 -14
  11. package/lib/types/client/compat/pane-chrome.d.ts +5 -0
  12. package/lib/types/client/compat/pane-icons.d.ts +2 -0
  13. package/lib/types/client/compat/pane-labels.d.ts +2 -0
  14. package/lib/types/client/compat/permission-confirmation.d.ts +5 -0
  15. package/lib/types/client/compat/permission-menu.d.ts +4 -1
  16. package/lib/types/client/compat/pet-anchor.d.ts +5 -1
  17. package/lib/types/client/compat/plugin-feedback.d.ts +28 -0
  18. package/lib/types/client/compat/reply-signature.d.ts +3 -2
  19. package/lib/types/client/compat/running-status.d.ts +8 -0
  20. package/lib/types/client/compat/scroll-pill.d.ts +4 -64
  21. package/lib/types/client/compat/sidebar-search.d.ts +18 -0
  22. package/lib/types/client/compat/status-groups.d.ts +1 -1
  23. package/lib/types/client/compat/tool-call-details.d.ts +10 -0
  24. package/lib/types/client/compat/tool-call-groups.d.ts +22 -0
  25. package/lib/types/client/compat/tool-call-labels.d.ts +5 -0
  26. package/lib/types/client/compat/tool-file-summary.d.ts +13 -0
  27. package/lib/types/client/compat/voice-waveform.d.ts +12 -0
  28. package/lib/types/client/compat/workspace-branch.d.ts +62 -0
  29. package/lib/types/client/compat/workspace-menu.d.ts +10 -1
  30. package/lib/types/client/compat/workspace-picker-port.d.ts +9 -0
  31. package/lib/types/client/contracts/ports.d.ts +1 -0
  32. package/lib/types/client/features/composer-pet/BlankPet.d.ts +4 -1
  33. package/lib/types/client/features/composer-pet/ClaudePet.d.ts +7 -0
  34. package/lib/types/client/features/composer-pet/ComposerPet.d.ts +1 -1
  35. package/lib/types/client/features/composer-pet/claude-frames.d.ts +12 -0
  36. package/lib/types/client/features/composer-pet/claude-motion.d.ts +3 -0
  37. package/lib/types/client/features/conversation/header-actions/BlankHeaderActions.d.ts +10 -0
  38. package/lib/types/client/features/conversation/header-actions/HeaderActionButton.d.ts +3 -2
  39. package/lib/types/client/features/conversation/header-actions/entries.d.ts +4 -5
  40. package/lib/types/client/features/conversation/header-actions/mount.d.ts +4 -5
  41. package/lib/types/client/features/model-controls/EffortPanel.d.ts +2 -1
  42. package/lib/types/client/features/model-controls/ModelControls.d.ts +11 -1
  43. package/lib/types/client/features/model-controls/PricingHint.d.ts +19 -0
  44. package/lib/types/client/features/model-controls/model-icon-data.d.ts +31 -0
  45. package/lib/types/client/features/model-controls/model-icon-match.d.ts +20 -0
  46. package/lib/types/client/features/model-controls/model-icons.d.ts +13 -0
  47. package/lib/types/client/features/model-controls/pricing.d.ts +18 -0
  48. package/lib/types/client/features/model-controls/selection.d.ts +2 -0
  49. package/lib/types/client/features/settings/ChoiceMenu.d.ts +15 -0
  50. package/lib/types/client/features/settings/fields.d.ts +18 -0
  51. package/lib/types/client/features/settings/locales.d.ts +22 -1
  52. package/lib/types/client/features/tool-calls/index.d.ts +2 -2
  53. package/lib/types/host/balance.d.ts +5 -0
  54. package/lib/types/host/git.d.ts +37 -0
  55. package/lib/types/host/index.d.ts +38 -0
  56. package/lib/types/shared/balance.d.ts +8 -0
  57. package/lib/types/shared/config.d.ts +14 -3
  58. package/lib/types/shared/git.d.ts +30 -0
  59. package/package.json +2 -1
package/lib/index.js CHANGED
@@ -2,31 +2,41 @@
2
2
  import z from "@deepseek-ai/schemastery";
3
3
 
4
4
  // src/shared/config.ts
5
+ var DEFAULT_PERSONALIZATION = Object.freeze({
6
+ petStyle: "claude",
7
+ runningStyle: "claude",
8
+ replySignatureStyle: "claude",
9
+ greetingStyle: "claude",
10
+ greetingName: ""
11
+ });
5
12
  var DEFAULT_FEATURES = Object.freeze({
6
13
  shell: true,
7
14
  sidebar: true,
8
15
  "new-session": true,
9
16
  conversation: true,
10
- "tool-calls": false,
11
- statistics: false,
17
+ "tool-calls": true,
18
+ statistics: true,
12
19
  "composer-pet": true,
13
20
  "composer-stats": false,
14
- "context-panel": true
21
+ "context-panel": true,
22
+ "hero-ripples": true
15
23
  });
16
24
  var DEFAULT_APPEARANCE = Object.freeze({
17
25
  canvas: "",
18
26
  sidebar: ""
19
27
  });
20
28
  var DEFAULT_FONTS = Object.freeze({
21
- uiLatin: "",
22
- uiCjk: "",
23
- code: ""
29
+ uiLatin: "Anthropic Sans Web",
30
+ uiCjk: "Anthropic Serif Web",
31
+ code: "SF Mono"
24
32
  });
25
33
  var DEFAULT_VIEW = Object.freeze({
26
34
  showEmptyGroups: false,
27
35
  groupByStatus: true,
28
36
  showHeaderSubagents: false,
29
- showHeaderTeam: false
37
+ showHeaderTeam: false,
38
+ showSidebarBrand: false,
39
+ showModelIcons: true
30
40
  });
31
41
  var FONT_NAME_MAX_LENGTH = 64;
32
42
 
@@ -166,7 +176,7 @@ function viewUsage(state) {
166
176
  const cached = views.get(state);
167
177
  if (cached !== void 0) return cached;
168
178
  const models = modelPairs(state.models);
169
- const view = {
179
+ const view2 = {
170
180
  prompts: state.prompts,
171
181
  tokens: state.tokens,
172
182
  days: pairs(state.days),
@@ -174,8 +184,8 @@ function viewUsage(state) {
174
184
  models,
175
185
  modelDays: modelDayRows(state.modelDays, models)
176
186
  };
177
- views.set(state, view);
178
- return view;
187
+ views.set(state, view2);
188
+ return view2;
179
189
  }
180
190
  function schemaOf(parse) {
181
191
  return { parse };
@@ -975,9 +985,9 @@ function createContextProjection(policy) {
975
985
  view(state) {
976
986
  const cached = views2.get(state);
977
987
  if (cached !== void 0) return cached;
978
- const view = viewContext(state, policy());
979
- views2.set(state, view);
980
- return view;
988
+ const view2 = viewContext(state, policy());
989
+ views2.set(state, view2);
990
+ return view2;
981
991
  }
982
992
  },
983
993
  stateVersion: CONTEXT_STATE_VERSION
@@ -1001,6 +1011,221 @@ function mountContextBreakdown(ctx, isEnabled) {
1001
1011
  });
1002
1012
  }
1003
1013
 
1014
+ // src/shared/balance.ts
1015
+ var BALANCE_PATH = "/api/ccd-deepseek-balance";
1016
+ function readCnyBalance(value) {
1017
+ if (!value || typeof value !== "object" || !("balance_infos" in value) || !Array.isArray(value.balance_infos)) return null;
1018
+ const row = value.balance_infos.find((row2) => row2?.currency === "CNY");
1019
+ const amount = (value2) => typeof value2 === "string" && /^-?\d+(?:\.\d+)?$/.test(value2) && Number.isFinite(Number(value2));
1020
+ if (!row || !amount(row.total_balance) || !amount(row.granted_balance) || !amount(row.topped_up_balance)) return null;
1021
+ return { total: row.total_balance, granted: row.granted_balance, toppedUp: row.topped_up_balance };
1022
+ }
1023
+
1024
+ // src/host/balance.ts
1025
+ async function readBalanceKey(ctx) {
1026
+ const loader = ctx.get("loader");
1027
+ const entry = loader && [...loader.entries()].find((entry2) => entry2.disabled !== true && entry2.options?.name === "@deepseek-ai/dsh-llm-deepseek-api-key");
1028
+ if (!entry) return void 0;
1029
+ const config = entry.options?.config;
1030
+ const environment = ctx.get("launchEnvironment");
1031
+ const baseURL = config?.baseURL ?? (environment ? environment.get("DEEPSEEK_BASE_URL")?.value : process.env["DEEPSEEK_BASE_URL"]) ?? "https://api.deepseek.com/anthropic";
1032
+ if (new URL(baseURL).origin !== "https://api.deepseek.com") return void 0;
1033
+ const ref = config?.apiKeyEnv ?? "DEEPSEEK_API_KEY";
1034
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(ref)) return void 0;
1035
+ const credentials = ctx.get("credentials");
1036
+ const key = credentials ? (await credentials.resolve(ref))?.value : environment ? environment.get(ref)?.value : process.env[ref];
1037
+ return key?.trim() || void 0;
1038
+ }
1039
+ function mountDeepSeekBalance(ctx, isEnabled) {
1040
+ ctx.inject(["connection"], (injected) => {
1041
+ if (!isEnabled()) return;
1042
+ const connection = injected.get("connection");
1043
+ if (!connection) return;
1044
+ ctx.effect(() => connection.fetch.register({
1045
+ path: BALANCE_PATH,
1046
+ methods: ["GET"],
1047
+ requestBody: "buffered",
1048
+ fetch: async (request) => {
1049
+ const reply = (state, balance = null) => Response.json({ state, balance }, {
1050
+ headers: { "cache-control": "no-store" }
1051
+ });
1052
+ if (!isEnabled()) return reply("unconfigured");
1053
+ try {
1054
+ const key = await readBalanceKey(injected);
1055
+ if (!key) return reply("unconfigured");
1056
+ const response = await fetch("https://api.deepseek.com/user/balance", {
1057
+ headers: { authorization: `Bearer ${key}`, accept: "application/json" },
1058
+ redirect: "error",
1059
+ signal: AbortSignal.any([request.signal, AbortSignal.timeout(1e4)])
1060
+ });
1061
+ if (response.status === 401 || response.status === 403) return reply("unauthorized");
1062
+ if (!response.ok) return reply("error");
1063
+ const balance = readCnyBalance(await response.json());
1064
+ return reply(balance ? "ready" : "no-cny", balance);
1065
+ } catch {
1066
+ return reply("error");
1067
+ }
1068
+ }
1069
+ }), "deepseek: balance route");
1070
+ });
1071
+ }
1072
+
1073
+ // src/host/git.ts
1074
+ import { execFile } from "node:child_process";
1075
+
1076
+ // src/shared/git.ts
1077
+ var GIT_BRANCHES_PATH = "/api/ccd-git-branches";
1078
+ var GIT_BRANCH_LIMIT = 200;
1079
+ var GIT_CHECKOUT_PATH = "/api/ccd-git-checkout";
1080
+
1081
+ // src/host/git.ts
1082
+ var runGit = (cwd, args, signal, timeout = 5e3) => new Promise((resolve, reject) => {
1083
+ execFile("git", ["-c", "core.fsmonitor=false", ...args], {
1084
+ cwd,
1085
+ signal,
1086
+ timeout,
1087
+ windowsHide: true,
1088
+ maxBuffer: 1 << 20,
1089
+ encoding: "utf8",
1090
+ env: { ...process.env, LC_ALL: "C", GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" }
1091
+ }, (error, stdout, stderr) => {
1092
+ if (error) reject(Object.assign(error, { stderr }));
1093
+ else resolve(stdout);
1094
+ });
1095
+ });
1096
+ var view = (state) => ({ state, current: null, detached: false, branches: [], truncated: false });
1097
+ async function listBranches(cwd, run, signal) {
1098
+ let listed;
1099
+ try {
1100
+ listed = await run(cwd, [
1101
+ "for-each-ref",
1102
+ "--sort=-committerdate",
1103
+ `--count=${GIT_BRANCH_LIMIT + 1}`,
1104
+ "--format=%(refname:short)",
1105
+ "refs/heads"
1106
+ ], signal);
1107
+ } catch (error) {
1108
+ const failure = error;
1109
+ if (failure.code === "ENOENT") return view("no-git");
1110
+ if (failure.code === 128 && /not a git repository/iu.test(String(failure.stderr ?? ""))) return view("not-repo");
1111
+ return view("error");
1112
+ }
1113
+ let current = null;
1114
+ let detached = false;
1115
+ try {
1116
+ current = (await run(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"], signal)).trim() || null;
1117
+ } catch {
1118
+ try {
1119
+ current = (await run(cwd, ["rev-parse", "--short", "HEAD"], signal)).trim() || null;
1120
+ detached = current !== null;
1121
+ } catch {
1122
+ }
1123
+ }
1124
+ const names = listed.split("\n").map((line) => line.trim()).filter(Boolean);
1125
+ const truncated = names.length > GIT_BRANCH_LIMIT;
1126
+ const others = names.slice(0, GIT_BRANCH_LIMIT).filter((name) => name !== current);
1127
+ const branches = current !== null && !detached ? [current, ...others] : others;
1128
+ return { state: "ready", current, detached, branches: branches.slice(0, GIT_BRANCH_LIMIT), truncated };
1129
+ }
1130
+ var outcome = (state, carried = 0, message = "") => ({ state, carried, message });
1131
+ var switching = /* @__PURE__ */ new Map();
1132
+ async function checkoutBranch(cwd, branch, run) {
1133
+ const previous = switching.get(cwd) ?? Promise.resolve();
1134
+ const attempt = previous.catch(() => {
1135
+ }).then(() => switchNow(cwd, branch, run));
1136
+ switching.set(cwd, attempt);
1137
+ try {
1138
+ return await attempt;
1139
+ } finally {
1140
+ if (switching.get(cwd) === attempt) switching.delete(cwd);
1141
+ }
1142
+ }
1143
+ async function switchNow(cwd, branch, run) {
1144
+ const signal = new AbortController().signal;
1145
+ if (branch === "" || branch.startsWith("-") || /[\0\n\r]/u.test(branch)) return outcome("invalid-branch");
1146
+ try {
1147
+ await run(cwd, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], signal);
1148
+ } catch {
1149
+ return outcome("invalid-branch");
1150
+ }
1151
+ try {
1152
+ await run(cwd, ["switch", branch], signal, 6e4);
1153
+ } catch (error) {
1154
+ const stderr = String(error.stderr ?? "");
1155
+ if (/would be overwritten by checkout|overwritten by switch|commit your changes or stash them/iu.test(stderr)) return outcome("conflict");
1156
+ if (/already (?:checked out|used by worktree)/iu.test(stderr)) return outcome("in-worktree");
1157
+ const line = stderr.split("\n").map((text) => text.trim()).find(Boolean) ?? "";
1158
+ return outcome("error", 0, line.replace(/^(?:fatal|error):\s*/iu, ""));
1159
+ }
1160
+ let carried = 0;
1161
+ try {
1162
+ carried = (await run(cwd, ["status", "--porcelain", "--untracked-files=no"], signal)).split("\n").filter((line) => line.trim() !== "").length;
1163
+ } catch {
1164
+ }
1165
+ return outcome("switched", carried);
1166
+ }
1167
+ function mountGitBranches(ctx, isEnabled, run = runGit) {
1168
+ ctx.inject(["connection"], (injected) => {
1169
+ if (!isEnabled()) return;
1170
+ const connection = injected.get("connection");
1171
+ if (!connection) return;
1172
+ ctx.effect(() => connection.fetch.register({
1173
+ path: GIT_BRANCHES_PATH,
1174
+ methods: ["GET"],
1175
+ requestBody: "buffered",
1176
+ fetch: async (request) => {
1177
+ const reply = (body) => Response.json(body, { headers: { "cache-control": "no-store" } });
1178
+ if (!isEnabled()) return reply(view("error"));
1179
+ const id = new URL(request.url, "http://host").searchParams.get("workspace") ?? "";
1180
+ const registry = injected.get("workspaceRegistry");
1181
+ let path;
1182
+ try {
1183
+ path = id === "" ? void 0 : registry?.get(id)?.path;
1184
+ } catch {
1185
+ path = void 0;
1186
+ }
1187
+ if (path === void 0) return reply(view("unknown-workspace"));
1188
+ try {
1189
+ return reply(await listBranches(path, run, request.signal));
1190
+ } catch {
1191
+ return reply(view("error"));
1192
+ }
1193
+ }
1194
+ }), "git: branch route");
1195
+ ctx.effect(() => connection.fetch.register({
1196
+ path: GIT_CHECKOUT_PATH,
1197
+ methods: ["POST"],
1198
+ requestBody: "buffered",
1199
+ fetch: async (request) => {
1200
+ const reply = (body2) => Response.json(body2, { headers: { "cache-control": "no-store" } });
1201
+ if (!isEnabled()) return reply(outcome("error"));
1202
+ let body;
1203
+ try {
1204
+ body = await request.json();
1205
+ } catch {
1206
+ body = void 0;
1207
+ }
1208
+ const fields = body && typeof body === "object" ? body : {};
1209
+ const id = typeof fields["workspace"] === "string" ? fields["workspace"] : "";
1210
+ const branch = typeof fields["branch"] === "string" ? fields["branch"] : "";
1211
+ const registry = injected.get("workspaceRegistry");
1212
+ let path;
1213
+ try {
1214
+ path = id === "" ? void 0 : registry?.get(id)?.path;
1215
+ } catch {
1216
+ path = void 0;
1217
+ }
1218
+ if (path === void 0) return reply(outcome("unknown-workspace"));
1219
+ try {
1220
+ return reply(await checkoutBranch(path, branch, run));
1221
+ } catch {
1222
+ return reply(outcome("error"));
1223
+ }
1224
+ }
1225
+ }), "git: checkout route");
1226
+ });
1227
+ }
1228
+
1004
1229
  // src/host/index.ts
1005
1230
  var HEX_COLOUR = /^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})?$/;
1006
1231
  var FONT_NAME = new RegExp(`^[\\p{L}\\p{N} ._-]{0,${FONT_NAME_MAX_LENGTH}}$`, "u");
@@ -1016,7 +1241,8 @@ var Config = z.object({
1016
1241
  statistics: z.boolean().default(DEFAULT_FEATURES.statistics),
1017
1242
  "composer-pet": z.boolean().default(DEFAULT_FEATURES["composer-pet"]),
1018
1243
  "composer-stats": z.boolean().default(DEFAULT_FEATURES["composer-stats"]),
1019
- "context-panel": z.boolean().default(DEFAULT_FEATURES["context-panel"])
1244
+ "context-panel": z.boolean().default(DEFAULT_FEATURES["context-panel"]),
1245
+ "hero-ripples": z.boolean().default(DEFAULT_FEATURES["hero-ripples"])
1020
1246
  }).default({ ...DEFAULT_FEATURES }).description("\u6309\u6A21\u5757\u542F\u7528").volatile(),
1021
1247
  /* One volatile node per section, exactly like `features`: Cordis rejects a
1022
1248
  volatile field nested inside another volatile field ("volatile fields
@@ -1031,7 +1257,16 @@ var Config = z.object({
1031
1257
  uiCjk: z.string().default(DEFAULT_FONTS.uiCjk).description("\u754C\u9762\u4E2D\u6587\u5B57\u4F53\u540D\uFF08\u7559\u7A7A\u7528\u7CFB\u7EDF\u6808\uFF09").pattern(FONT_NAME),
1032
1258
  code: z.string().default(DEFAULT_FONTS.code).description("\u4EE3\u7801\u4E0E\u7B49\u5BBD\u5B57\u4F53\u540D\uFF08\u7559\u7A7A\u7528\u7CFB\u7EDF\u6808\uFF09").pattern(FONT_NAME)
1033
1259
  }).default({ ...DEFAULT_FONTS }).description("\u5B57\u4F53").volatile(),
1260
+ personalization: z.object({
1261
+ petStyle: z.union(["whale", "claude"]).default(DEFAULT_PERSONALIZATION.petStyle).description("\u8F93\u5165\u6846\u5BA0\u7269\u6837\u5F0F"),
1262
+ runningStyle: z.union(["whale", "claude"]).default(DEFAULT_PERSONALIZATION.runningStyle).description("\u804A\u5929\u8F93\u51FA\u4E2D\u7684\u7B49\u5F85\u56FE\u6807\u4E0E\u72B6\u6001\u6837\u5F0F"),
1263
+ replySignatureStyle: z.union(["whale", "claude"]).default(DEFAULT_PERSONALIZATION.replySignatureStyle).description("\u56DE\u590D\u7ED3\u675F\u540E\u7684\u7B7E\u540D\u56FE\u6807\u6837\u5F0F"),
1264
+ greetingStyle: z.union(["explore", "claude"]).default(DEFAULT_PERSONALIZATION.greetingStyle).description("\u9996\u9875\u95EE\u5019\u8BED\u4E0E\u56FE\u6807\u6837\u5F0F"),
1265
+ greetingName: z.string().default("").pattern(/^.{0,64}$/u).description("\u95EE\u5019\u8BED\u4E2D\u7684\u79F0\u547C\uFF08\u53EF\u7559\u7A7A\uFF09")
1266
+ }).default({ ...DEFAULT_PERSONALIZATION }).description("\u4E2A\u6027\u5316").volatile(),
1034
1267
  view: z.object({
1268
+ showModelIcons: z.boolean().default(DEFAULT_VIEW.showModelIcons).description("\u663E\u793A\u6A21\u578B\u9009\u5355\u4E2D\u7684\u524D\u7F6E\u56FE\u6807\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09"),
1269
+ showSidebarBrand: z.boolean().default(DEFAULT_VIEW.showSidebarBrand).description("\u663E\u793A\u4FA7\u680F DeepSeek Harness \u54C1\u724C\u6761\u76EE\uFF08\u9ED8\u8BA4\u5173\u95ED\uFF09"),
1035
1270
  showHeaderSubagents: z.boolean().default(DEFAULT_VIEW.showHeaderSubagents).description("\u663E\u793A\u9876\u680F\u5B50\u4EE3\u7406\u5165\u53E3\uFF08\u4EC5\u63A7\u5236\u5165\u53E3\u663E\u793A\uFF09"),
1036
1271
  showHeaderTeam: z.boolean().default(DEFAULT_VIEW.showHeaderTeam).description("\u663E\u793A\u9876\u680F\u56E2\u961F\u5165\u53E3\uFF08\u4EC5\u63A7\u5236\u5165\u53E3\u663E\u793A\uFF09"),
1037
1272
  groupByStatus: z.boolean().default(DEFAULT_VIEW.groupByStatus).description("\u6309\u4F1A\u8BDD\u72B6\u6001\u5206\u7EC4\uFF08\u4FDD\u7559\u539F\u751F\u9879\u76EE\u89C6\u56FE\u9009\u9879\uFF09"),
@@ -1053,6 +1288,8 @@ function apply(ctx, config) {
1053
1288
  const host = typeof config === "object" && config !== null ? config : {};
1054
1289
  mountStatistics(ctx, () => readValue(host.enabled, false) === true);
1055
1290
  mountContextBreakdown(ctx, () => readValue(host.enabled, false) === true);
1291
+ mountDeepSeekBalance(ctx, () => readValue(host.enabled, false) === true);
1292
+ mountGitBranches(ctx, () => readValue(host.enabled, false) === true);
1056
1293
  }
1057
1294
  export {
1058
1295
  Config,
@@ -0,0 +1,8 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Last completed display, retained across menu openings for the same account. */
3
+ export interface AccountBalanceCache {
4
+ text?: string;
5
+ title?: string;
6
+ }
7
+ /** One usage row in the lower-left account menu; polling lives only while open. */
8
+ export declare function mountAccountBalance(menu: HTMLElement, cache?: AccountBalanceCache): Disposer;
@@ -2,10 +2,9 @@ import type { Disposer } from '../contracts/ports.ts';
2
2
  /**
3
3
  * The name the browsing region's own title line draws.
4
4
  * @param text - the host's word, exactly as it rendered it.
5
- * @param noHistory - whether the plugin's no-history prompt is on screen.
6
5
  * @returns the reference's name, or null to leave the host's own word.
7
6
  */
8
- export declare function browserHeaderWord(text: string, noHistory: boolean): string | null;
7
+ export declare function browserHeaderWord(text: string): string | null;
9
8
  /**
10
9
  * Name the browsing region's separate toolbar.
11
10
  *
@@ -0,0 +1,7 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Only the host's count title is normalized; file names are never translated. */
3
+ export declare function changedFilesTitle(title: string): string | undefined;
4
+ /** Decorate native completed-turn summaries without moving React-owned nodes.
5
+ * Native headers/rows retain review and hover-preview handlers. The added
6
+ * footer delegates to the header, and decorative icons copy its native glyph. */
7
+ export declare function mountChangedFiles(document: Document, report: (error: unknown) => void): Disposer;
@@ -1,56 +1,3 @@
1
1
  import type { Disposer } from '../contracts/ports.ts';
2
- /**
3
- * The Composer status bar's seat order, and the context ring's accessible name.
4
- *
5
- * Host facts (DSH `0.2.0-rc.2`, read from the installed `app.asar`):
6
- *
7
- * - The status bar is `ui-conversation`'s `InputBar` control row
8
- * (`.yhfFVG_row`). Its flex children are the leading `.yhfFVG_tools` group —
9
- * command button, then `.yhfFVG_modes` holding the `conversation.input.permission`
10
- * and `conversation.input.plan` seats — and the trailing `.yhfFVG_trailing`
11
- * group — the model seat, then the `conversation.input.activity` seat, then the
12
- * absolutely positioned send/stop buttons.
13
- * - `@deepseek-ai/dsh-experimental-client-ui-voice-input` registers its control
14
- * into `conversation.input.activity`. The renderer seats every slot entry in a
15
- * `display: contents` outlet (`div[data-slot="…"]`, see
16
- * `dsh-client-ui-renderer`'s `SlotOutlet`), so the seat the slot occupies is
17
- * that outlet's parent element.
18
- * - `ContextMeter` (`ui-conversation`'s skeleton) renders the ring's trigger
19
- * with an accessible name carrying the reading, and returns nothing at all
20
- * until the token meter knows both a numerator and the context window.
21
- *
22
- * The voice control therefore sits one flex container away from the permission
23
- * control it is asked to follow, and `order` cannot cross containers: the seat
24
- * itself has to move. It is moved into `.yhfFVG_modes`, immediately after the
25
- * permission seat and before the plan seat, and marked with
26
- * {@link VOICE_SEAT_ATTRIBUTE} so the stylesheet and the release path can find
27
- * it.
28
- *
29
- * Moving a node React owns is only safe while React never removes that node from
30
- * the parent it was moved out of. It does not: `InputBar` renders the seat under
31
- * two guards, `input === void 0` and `sessionId === void 0` — `input` is the
32
- * shell's own store snapshot, which exists as soon as the shell does — and the
33
- * `conversation.composer.bar` entry that owns the whole Composer is a
34
- * `session-maybe` slot, whose documented contract is that such an entry adopts
35
- * its first session in place and remounts, as one deleted subtree, on every later
36
- * identity change. Within one instance the seat is therefore only ever inserted,
37
- * never removed. Moving the *ring* instead is not available at all: `InputBar`
38
- * unmounts the ring whenever the voice control expands, and React would then call
39
- * `removeChild` on a parent the node no longer belongs to.
40
- *
41
- * The seat goes back to its recorded host position — and the mark comes off —
42
- * whenever the host switches the row to the voice control's capture layout
43
- * (`InputBar` hides `.yhfFVG_tools` and lets `.yhfFVG_trailing` span the row), so
44
- * that layout keeps the host's own geometry, and again on release.
45
- */
46
- /** Mark on a relocated voice seat; the composer sheet reads it. */
47
- export declare const VOICE_SEAT_ATTRIBUTE = "data-ccd-voice-seat";
48
- /**
49
- * Keep the voice control beside the permission control, and the context ring's
50
- * accessible name free of the percentage it no longer shows.
51
- *
52
- * @param document - renderer document carrying the Composer.
53
- * @param report - sink for observer failures; the host's own order stays.
54
- * @returns disposer that restores every seat and label it touched.
55
- */
2
+ /** Name context rings and suppress the host's portaled dictation hint. */
56
3
  export declare function mountComposerStatusOrder(document: Document, report: (error: unknown) => void): Disposer;
@@ -0,0 +1,6 @@
1
+ import type { PersonalizationConfig } from '../../shared/config.ts';
2
+ import type { Disposer } from '../contracts/ports.ts';
3
+ /** The greeting follows the interface language and never includes an account name. */
4
+ export declare function claudeGreeting(language?: string): string;
5
+ /** Add a reversible presentation beside the native heading without taking its React nodes. */
6
+ export declare function mountHeroGreeting(document: Document, config: PersonalizationConfig): Disposer;
@@ -42,6 +42,8 @@
42
42
  * - `packages/client/ui-settings/src/client/SettingsRoot.module.css` (`Dws9Sa_`)
43
43
  * - `packages/client/ui-settings-account/src/client/AccountMenu.module.css` (`ZogL4G_`)
44
44
  * - `packages/client/ui-primitives/lib/Menu.module.css` (`_4ub78_`, `_ri079_`)
45
+ * - `packages/client/ui-primitives/lib/Modal.module.css` (`_o6lrb_`) and
46
+ * `RiskConfirmation.module.css` (`_cjd8z_`), shared static frontend styles
45
47
  * - `packages/client/ui-primitives/lib/Menu.js` (`MenuItemButton`: the icon seat
46
48
  * `account-menu.css` scales; its `[role="menuitem"] > span` structure)
47
49
  * - `packages/client/ui-chat/src/client/chat/AssistantMarkdown.module.css` (`gKv1-q_`)
@@ -127,6 +129,7 @@ export declare const HOST: Readonly<{
127
129
  readonly filesBody: '[data-files-body]';
128
130
  readonly filesEntry: '[data-files-entry]';
129
131
  readonly sidebarRoot: '._3WPZCG_root';
132
+ readonly sidebarCollapsed: '._3WPZCG_collapsed';
130
133
  readonly sidebarLogoRow: '._3WPZCG_logoRow';
131
134
  readonly sidebarBrand: '._3WPZCG_brand';
132
135
  readonly sidebarBrandName: '._3WPZCG_brandName';
@@ -166,6 +169,9 @@ export declare const HOST: Readonly<{
166
169
  /** StateDot's stable state attributes, used when root sessionStatus is absent. */
167
170
  readonly sessionNeedsInput: '[aria-hidden="true"][data-state="warning"]';
168
171
  readonly sessionWorking: '[aria-hidden="true"][data-state="ongoing"]';
172
+ /** Rows.module.css: the Session row the main panel is showing. A folded
173
+ status section keeps this one row visible. */
174
+ readonly sessionRowSelected: '.jJkEga_selected';
169
175
  /** Rows.module.css: one Workspace's row, the line the merged header shares,
170
176
  and its trailing action group (`sidebar.css` parks both). */
171
177
  readonly workspaceRow: '.jJkEga_projectRow';
@@ -326,6 +332,11 @@ export declare const HOST: Readonly<{
326
332
  children, so the statistics card is appended here and positioned against
327
333
  the greeting. */
328
334
  readonly heroComposerStack: '.ST7X_W_composerStack';
335
+ /** Shared frontend RiskConfirmation DOM, identical in both desktop builds. */
336
+ readonly riskConfirmation: '._confirmation_cjd8z_1[role="dialog"]';
337
+ readonly riskAcknowledgement: '._acknowledgement_cjd8z_38';
338
+ readonly riskConfirmAction: '._confirmAction_cjd8z_71';
339
+ readonly riskCancelAction: '._modalAction_cjd8z_67';
329
340
  /** Every menu surface the `Menu` primitive renders, portal or in place. */
330
341
  readonly menu: '[role="menu"]';
331
342
  readonly menuItem: '[role="menuitem"]';
@@ -350,6 +361,11 @@ export declare const HOST: Readonly<{
350
361
  * means the frame is not the pinned one and the feature must stay off.
351
362
  */
352
363
  export declare const ANCHOR: Readonly<{
364
+ /** ChatNodeSeat / ToolCallTree: immutable transcript seats and atomic calls. */
365
+ readonly chatFlowItem: '[data-chat-flow-kind]';
366
+ readonly toolFlow: '[data-chat-flow-kind="tool-call"]';
367
+ readonly reasoningFlow: '[data-chat-group-part="reasoning"]';
368
+ readonly toolCall: '[data-chat-call-id]';
353
369
  /** ui-chat TurnProcessNodeView / ChatGroupSeat: process containers, not leaf details. */
354
370
  readonly turnProcess: 'button[data-turn-process]';
355
371
  readonly stepProcessToggle: '[data-step-process] > div > button[data-process-activity]';
@@ -1,17 +1,3 @@
1
- /**
2
- * Whether a mutation batch can concern one region of the page.
3
- *
4
- * Several modules here observe the whole body because the host can move or
5
- * rebuild their anchor at any time, then pay a geometry pass on every batch.
6
- * Most batches, though, come from somewhere else — a drag in the right pane, a
7
- * streaming transcript on another page, another plugin writing its own markup.
8
- * This predicate lets a module skip those batches without narrowing what it
9
- * observes, so an anchor that is replaced still reaches it.
10
- *
11
- * The answer is deliberately conservative: a region that is missing, detached
12
- * or cannot answer `contains` makes every batch relevant, so a module that
13
- * cannot yet measure is never starved of the mutation that would let it.
14
- */
15
1
  /**
16
2
  * Whether any record in a batch can move something inside `region`.
17
3
  *
@@ -41,3 +27,21 @@ export declare function recordsTouch(records: readonly MutationRecord[], region:
41
27
  * @returns true when an inserted element is, or contains, a match.
42
28
  */
43
29
  export declare function recordsIntroduce(records: readonly MutationRecord[], selector: string): boolean;
30
+ /**
31
+ * Whether every record in a batch stayed inside the transcript's chat seats.
32
+ *
33
+ * While a reply streams, nearly every batch is text and markup growing inside
34
+ * one `[data-chat-flow-kind]` seat. A module that only dresses the sidebar,
35
+ * the Composer, a menu or a dialog cannot be affected by those writes, and a
36
+ * document-wide `querySelectorAll` per streamed batch is the largest cost this
37
+ * plugin adds to the host's own rendering. Such a module returns early on
38
+ * these batches; a seat being inserted or removed is written on its parent,
39
+ * outside every seat, so structural changes still reach it.
40
+ *
41
+ * The answer is conservative: a missing or empty batch, or a target that cannot answer
42
+ * `closest`, is not confined, and the module looks again as it always did.
43
+ *
44
+ * @param records - the observer's batch.
45
+ * @returns true when the batch can be skipped by a module outside the transcript.
46
+ */
47
+ export declare function recordsInTranscript(records: readonly MutationRecord[]): boolean;
@@ -14,6 +14,11 @@ export interface PaneChromeOptions {
14
14
  title: string;
15
15
  selected: boolean;
16
16
  }[];
17
+ readonly browsers: readonly {
18
+ id: string;
19
+ title: string;
20
+ selected: boolean;
21
+ }[];
17
22
  readonly files: readonly {
18
23
  id: string;
19
24
  title: string;
@@ -17,6 +17,8 @@ declare const ICONS: {
17
17
  readonly Files: "<svg viewBox=\"0 0 1000 1000\" fill=\"currentColor\" aria-hidden=\"true\"><path transform=\"translate(0 1000) scale(1 -1)\" d=\"M785 247H415Q396 247 380.5 256.0Q365 265 356.0 280.5Q347 296 347 315V835Q347 854 356.0 869.5Q365 885 380.5 894.0Q396 903 415 903H666Q693 903 713 884L833 767Q853 747 853 718V315Q853 296 844.0 280.5Q835 265 819.5 256.0Q804 247 785 247ZM785 303Q790 303 793.5 306.5Q797 310 797 315V718L793 727L674 844L666 847H415Q410 847 406.5 843.5Q403 840 403 835V315Q403 310 406.5 306.5Q410 303 415 303ZM375 753V697H215Q210 697 206.5 693.5Q203 690 203 685V165Q203 160 206.5 156.5Q210 153 215 153H585Q590 153 593.5 156.5Q597 160 597 165V275H653V165Q653 146 644.0 130.5Q635 115 619.5 106.0Q604 97 585 97H215Q196 97 180.5 106.0Q165 115 156.0 130.5Q147 146 147 165V685Q147 704 156.0 719.5Q165 735 180.5 744.0Q196 753 215 753ZM622 850Q622 862 630.0 870.0Q638 878 650 878Q662 878 670.0 870.0Q678 862 678 850V750Q678 741 684.5 734.5Q691 728 700 728H800Q812 728 820.0 720.0Q828 712 828 700Q828 688 820.0 680.0Q812 672 800 672H700Q678 672 660.5 682.5Q643 693 632.5 710.5Q622 728 622 750Z\"/></svg>";
18
18
  readonly SidebarClose: "<svg viewBox=\"0 0 1000 1000\" fill=\"currentColor\" aria-hidden=\"true\"><path transform=\"translate(0 1000) scale(1 -1)\" d=\"M147 825Q147 837 155.0 845.0Q163 853 175 853Q187 853 195.0 845.0Q203 837 203 825V175Q203 163 195.0 155.0Q187 147 175 147Q163 147 155.0 155.0Q147 163 147 175ZM825 528Q837 528 845.0 520.0Q853 512 853 500Q853 488 845.0 480.0Q837 472 825 472H393L570 295Q578 287 578.0 275.0Q578 263 570 255Q562 247 550.0 247.0Q538 247 530 255L305 480Q296 489 296.5 500.5Q297 512 304.5 520.0Q312 528 325 528ZM530 745Q538 753 550.0 753.0Q562 753 570 745Q578 737 578.0 725.0Q578 713 570 705L345 480L305 520Z\"/></svg>";
19
19
  readonly SidebarOpen: "<svg viewBox=\"0 0 1000 1000\" fill=\"currentColor\" aria-hidden=\"true\"><path transform=\"translate(0 1000) scale(1 -1)\" d=\"M797 825Q797 837 805.0 845.0Q813 853 825 853Q837 853 845.0 845.0Q853 837 853 825V175Q853 163 845.0 155.0Q837 147 825 147Q813 147 805.0 155.0Q797 163 797 175ZM175 472Q163 472 155.0 480.0Q147 488 147 500Q147 512 155.0 520.0Q163 528 175 528H675Q688 528 695.5 520.0Q703 512 703.5 500.5Q704 489 695 480L470 255Q462 247 450.0 247.0Q438 247 430 255Q422 263 422.0 275.0Q422 287 430 295L607 472ZM430 705Q422 713 422.0 725.0Q422 737 430 745Q438 753 450.0 753.0Q462 753 470 745L695 520L655 480Z\"/></svg>";
20
+ readonly ArrowLeft: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 12H4m7-7-7 7 7 7"/></svg>';
21
+ readonly ArrowRight: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12h16m-7-7 7 7-7 7"/></svg>';
20
22
  };
21
23
  export type PaneIcon = keyof typeof ICONS;
22
24
  export declare function paneIcon(name: PaneIcon): string;
@@ -58,6 +58,8 @@ export interface PaneChromeLabels {
58
58
  }
59
59
  /** The capture menu's one row, and the webview area's two states. */
60
60
  export interface BrowserPaneLabels {
61
+ readonly newTab: string;
62
+ readonly tabs: string;
61
63
  readonly saveScreenshot: string;
62
64
  readonly awaitingServer: string;
63
65
  readonly enterAddress: string;
@@ -0,0 +1,5 @@
1
+ import type { Disposer } from '../contracts/ports.ts';
2
+ /** Remove the redundant acknowledgement step from permission confirmations.
3
+ * The native handler still updates React; only the user can press Enable.
4
+ * Other RiskConfirmation callers retain their acknowledgement controls. */
5
+ export declare function mountPermissionConfirmation(document: Document, report: (error: unknown) => void): Disposer;
@@ -36,6 +36,9 @@ export interface PermissionCopy {
36
36
  * @returns Chinese copy for a Chinese document, English otherwise.
37
37
  */
38
38
  export declare function permissionCopy(document: Document): PermissionCopy;
39
+ /** Short summaries of DSH's four built-in Agent presets. */
40
+ export declare function agentPresetCopy(document: Document): PermissionCopy;
41
+ export declare function mountAgentPresetMenu(document: Document, report: (error: unknown) => void): Disposer;
39
42
  /**
40
43
  * The permission picker's card, drawn the way the reference draws it.
41
44
  *
@@ -54,7 +57,7 @@ export declare function permissionCopy(document: Document): PermissionCopy;
54
57
  * `permissionCopy` above. It is kept in one table per document language, so a
55
58
  * wording change is one edit in one place.
56
59
  *
57
- * Three facts decide how the rows are reached, and each is worth stating:
60
+ * Three facts decide how the permission rows are reached, and each is worth stating:
58
61
  *
59
62
  * - **A row is recognised by its name.** `Menu` rows carry no identity of their
60
63
  * own — no id, no data attribute, and the preset's own glyph is a thing this
@@ -27,4 +27,8 @@ export declare const PET_INSET = 12;
27
27
  * selects new sessions; hidden, missing or unlaid-out cards publish null.
28
28
  * @returns position source whose dispose releases all listeners and observers.
29
29
  */
30
- export declare function createPetAnchor(document: Document, report: (error: unknown) => void): PetAnchorSource;
30
+ export declare function createPetAnchor(document: Document, report: (error: unknown) => void, box?: {
31
+ readonly width: number;
32
+ readonly height: number;
33
+ readonly inset?: number;
34
+ }): PetAnchorSource;
@@ -0,0 +1,28 @@
1
+ /** New-issue form of the plugin repository; title and body arrive as query parameters. */
2
+ export declare const FEEDBACK_ISSUE_URL = "https://github.com/zkforge/dsh-claude-desktop-theme/issues/new";
3
+ /** One diagnostic row appended under the description. */
4
+ export type FeedbackEnvironment = readonly (readonly [label: string, value: string])[];
5
+ /**
6
+ * The new-issue URL for one description.
7
+ *
8
+ * GitHub's `issues/new` reads `title` and `body` from the query string, so the
9
+ * report opens already filled in and the reporter still reviews and submits it
10
+ * on GitHub themself. Nothing is sent from the plugin.
11
+ *
12
+ * @param description - what the reporter typed; trimmed and capped.
13
+ * @param environment - diagnostic rows rendered as a table below it.
14
+ * @returns the prefilled issue URL.
15
+ */
16
+ export declare function feedbackIssueUrl(description: string, environment: FeedbackEnvironment): string;
17
+ /**
18
+ * Open the plugin's feedback dialog.
19
+ *
20
+ * The card follows the host's own "send feedback" sheet: a title, a description
21
+ * box, one line saying what the report will carry, and Cancel / Send. Send
22
+ * hands the description to GitHub's new-issue form in the browser; with no
23
+ * description it opens the blank form.
24
+ *
25
+ * @param document - renderer document the modal is shown in.
26
+ * @returns disposer that closes and removes the dialog.
27
+ */
28
+ export declare function openPluginFeedback(document: Document): () => void;
@@ -1,6 +1,7 @@
1
+ import type { PersonalizationConfig } from '../../shared/config.ts';
1
2
  import type { Disposer } from '../contracts/ports.ts';
2
3
  /** The host marks only the latest turn ending in a reply as always revealed.
3
4
  * Starting another turn clears that marker before its response is complete. */
4
5
  export declare const REPLY_SIGNATURE_SEAT = "[data-turn-tail][data-actions-reveal=\"always\"]:has(> [data-clock=\"end\"])";
5
- /** Keep one playful signature at the latest settled reply, following host swaps. */
6
- export declare function mountReplySignature(document: Document, report: (error: unknown) => void): Disposer;
6
+ /** Keep one static decorative signature at the latest settled reply, following host swaps. */
7
+ export declare function mountReplySignature(document: Document, report: (error: unknown) => void, style?: PersonalizationConfig['replySignatureStyle']): Disposer;
@@ -0,0 +1,8 @@
1
+ import type { PersonalizationConfig } from '../../shared/config.ts';
2
+ import type { Disposer } from '../contracts/ports.ts';
3
+ export type RunningPhase = 'waiting' | 'thinking' | 'tool' | 'responding' | 'approval';
4
+ /** Native clock owns time, including when reopening a turn already in progress. */
5
+ export declare function runningDuration(label: string): string;
6
+ export declare function runningPhase(root: Element): RunningPhase;
7
+ /** Add a presentation sibling, leaving React's whale, clock and text untouched. */
8
+ export declare function mountRunningStatus(document: Document, style: PersonalizationConfig['runningStyle'], report: (error: unknown) => void): Disposer;