@wacry/codeks-bridge 0.0.0-stage → 0.0.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.
Files changed (99) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +13 -0
  3. package/README.md +40 -2
  4. package/catalog/README.md +63 -0
  5. package/catalog/claude-opus-5-5.json +194 -0
  6. package/catalog/claude-sonnet-5-5.json +194 -0
  7. package/catalog/deepseek-flash.json +203 -0
  8. package/catalog/gpt-5.6-sol.json +152 -0
  9. package/catalog/gpt-6-astra.json +184 -0
  10. package/catalog/gpt-6-luna.json +178 -0
  11. package/catalog/gpt-6-sol.json +182 -0
  12. package/catalog/gpt-6.1-sol.json +187 -0
  13. package/dist/auth.d.ts +2 -0
  14. package/dist/auth.js +42 -0
  15. package/dist/backends/anthropic.d.ts +8 -0
  16. package/dist/backends/anthropic.js +309 -0
  17. package/dist/backends/chat.d.ts +16 -0
  18. package/dist/backends/chat.js +284 -0
  19. package/dist/backends/deepseek.d.ts +6 -0
  20. package/dist/backends/deepseek.js +37 -0
  21. package/dist/backends/responses.d.ts +5 -0
  22. package/dist/backends/responses.js +205 -0
  23. package/dist/carrier.d.ts +22 -0
  24. package/dist/carrier.js +61 -0
  25. package/dist/catalog.d.ts +8 -0
  26. package/dist/catalog.js +40 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +64 -0
  29. package/dist/compaction-handoff.d.ts +13 -0
  30. package/dist/compaction-handoff.js +126 -0
  31. package/dist/compaction-history.d.ts +7 -0
  32. package/dist/compaction-history.js +20 -0
  33. package/dist/compaction.d.ts +20 -0
  34. package/dist/compaction.js +134 -0
  35. package/dist/config.d.ts +6 -0
  36. package/dist/config.js +167 -0
  37. package/dist/errors.d.ts +43 -0
  38. package/dist/errors.js +141 -0
  39. package/dist/headers.d.ts +6 -0
  40. package/dist/headers.js +39 -0
  41. package/dist/history.d.ts +7 -0
  42. package/dist/history.js +85 -0
  43. package/dist/ids.d.ts +1 -0
  44. package/dist/ids.js +4 -0
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.js +4 -0
  47. package/dist/inspect/capture.d.ts +119 -0
  48. package/dist/inspect/capture.js +370 -0
  49. package/dist/inspect/http.d.ts +7 -0
  50. package/dist/inspect/http.js +149 -0
  51. package/dist/inspect/view.d.ts +239 -0
  52. package/dist/inspect/view.js +388 -0
  53. package/dist/model-catalog.d.ts +18 -0
  54. package/dist/model-catalog.js +16 -0
  55. package/dist/profiles.d.ts +6 -0
  56. package/dist/profiles.js +63 -0
  57. package/dist/prompts.d.ts +5 -0
  58. package/dist/prompts.js +118 -0
  59. package/dist/server.d.ts +4 -0
  60. package/dist/server.js +232 -0
  61. package/dist/sse.d.ts +12 -0
  62. package/dist/sse.js +93 -0
  63. package/dist/tool-history.d.ts +4 -0
  64. package/dist/tool-history.js +72 -0
  65. package/dist/tools.d.ts +33 -0
  66. package/dist/tools.js +182 -0
  67. package/dist/types.d.ts +115 -0
  68. package/dist/types.js +1 -0
  69. package/dist/upstream.d.ts +3 -0
  70. package/dist/upstream.js +84 -0
  71. package/dist/writer.d.ts +95 -0
  72. package/dist/writer.js +430 -0
  73. package/docs/backends-anthropic.md +61 -0
  74. package/docs/backends-deepseek-chat.md +75 -0
  75. package/docs/backends-responses.md +33 -0
  76. package/docs/compaction.md +176 -0
  77. package/docs/config.md +231 -0
  78. package/docs/design.md +93 -0
  79. package/docs/inspect.md +138 -0
  80. package/docs/reasoning.md +72 -0
  81. package/docs/releasing.md +50 -0
  82. package/docs/responses-stream.md +90 -0
  83. package/docs/testing.md +177 -0
  84. package/docs/tools.md +55 -0
  85. package/examples/config.json +35 -0
  86. package/package.json +46 -4
  87. package/ui/api.js +64 -0
  88. package/ui/app.css +375 -0
  89. package/ui/app.js +489 -0
  90. package/ui/detail.js +682 -0
  91. package/ui/diff.js +281 -0
  92. package/ui/dom.js +258 -0
  93. package/ui/events.js +351 -0
  94. package/ui/favicon.svg +1 -0
  95. package/ui/index.html +58 -0
  96. package/ui/labels.js +101 -0
  97. package/ui/list.js +180 -0
  98. package/ui/structure.js +285 -0
  99. package/ui/tree.js +158 -0
@@ -0,0 +1,35 @@
1
+ {
2
+ "host": "127.0.0.1",
3
+ "port": 8787,
4
+ "upstreams": {
5
+ "openai": {
6
+ "base_url": "https://api.openai.com/v1",
7
+ "api_key_env": "OPENAI_API_KEY"
8
+ },
9
+ "anthropic": {
10
+ "base_url": "https://api.anthropic.com/v1",
11
+ "api_key_env": "ANTHROPIC_API_KEY"
12
+ },
13
+ "deepseek": {
14
+ "base_url": "https://api.deepseek.com",
15
+ "api_key_env": "DEEPSEEK_API_KEY"
16
+ }
17
+ },
18
+ "models": {
19
+ "gpt": {
20
+ "upstream": "openai",
21
+ "model": "gpt-6.1-sol",
22
+ "catalog": "gpt-6.1-sol"
23
+ },
24
+ "claude": {
25
+ "upstream": "anthropic",
26
+ "model": "claude-sonnet-5-5",
27
+ "catalog": "claude-sonnet-5-5"
28
+ },
29
+ "deepseek": {
30
+ "upstream": "deepseek",
31
+ "model": "deepseek-flash",
32
+ "catalog": "deepseek-flash"
33
+ }
34
+ }
35
+ }
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@wacry/codeks-bridge",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "Configurable local Responses API bridge for codeks with GPT, Claude Messages, and DeepSeek Chat adapters.",
5
+ "license": "Apache-2.0",
6
+ "author": "David Zhang",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/WAcry/codeks-bridge.git"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "codeks-bridge": "dist/cli.js"
14
+ },
15
+ "exports": {
16
+ ".": "./dist/index.js"
17
+ },
18
+ "types": "./dist/index.d.ts",
19
+ "publishConfig": {
20
+ "access": "public",
21
+ "registry": "https://registry.npmjs.org"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "catalog",
26
+ "docs",
27
+ "examples",
28
+ "ui",
29
+ "README.md",
30
+ "LICENSE",
31
+ "NOTICE"
32
+ ],
33
+ "engines": {
34
+ "node": ">=22.18"
35
+ },
36
+ "scripts": {
37
+ "prepack": "npm run build",
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "typecheck": "tsc --noEmit -p tsconfig.json",
40
+ "test": "node --test \"test/**/*.test.ts\"",
41
+ "check": "node scripts/check.ts",
42
+ "dev": "node src/cli.ts serve"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.18.0",
46
+ "typescript": "^5.9.0"
47
+ }
48
+ }
package/ui/api.js ADDED
@@ -0,0 +1,64 @@
1
+ // Access to the inspector API (/inspect/api/*) and to imported export files.
2
+ // URLs are relative, so the page works under /inspect/ on any host.
3
+
4
+ const TOKEN_KEY = 'codeks-inspector-token';
5
+ export const EXPORT_FORMAT = 'codeks-bridge-capture';
6
+
7
+ export class ApiError extends Error {
8
+ constructor(status, message, code) {
9
+ super(message);
10
+ this.status = status;
11
+ this.code = code;
12
+ }
13
+ }
14
+
15
+ export const getToken = () => sessionStorage.getItem(TOKEN_KEY);
16
+ export const setToken = (t) => (t ? sessionStorage.setItem(TOKEN_KEY, t) : sessionStorage.removeItem(TOKEN_KEY));
17
+
18
+ /** Calls the API. Throws ApiError (status 0 = network failure). */
19
+ export async function api(path, { method = 'GET', json, raw = false } = {}) {
20
+ const headers = {};
21
+ const token = getToken();
22
+ if (token) headers.authorization = `Bearer ${token}`;
23
+ let body;
24
+ if (json !== undefined) {
25
+ headers['content-type'] = 'application/json';
26
+ body = JSON.stringify(json);
27
+ }
28
+ let res;
29
+ try {
30
+ res = await fetch(`api/${path}`, { method, headers, body, cache: 'no-store', credentials: 'same-origin' });
31
+ } catch (e) {
32
+ throw new ApiError(0, `无法连接 bridge:${e.message || e}`);
33
+ }
34
+ if (!res.ok) {
35
+ let code = null;
36
+ let message = `${res.status} ${res.statusText}`;
37
+ try {
38
+ const j = await res.json();
39
+ code = j?.error?.code ?? null;
40
+ if (j?.error?.message) message = `${res.status}:${j.error.message}`;
41
+ } catch { /* not JSON */ }
42
+ throw new ApiError(res.status, message, code);
43
+ }
44
+ return raw ? res : res.json();
45
+ }
46
+
47
+ /** Validates an export file and returns its exchanges (Detail objects). */
48
+ export function parseExport(text) {
49
+ let data;
50
+ try { data = JSON.parse(text); } catch (e) { throw new Error(`不是 JSON 文件:${e.message}`); }
51
+ if (data?.format !== EXPORT_FORMAT) throw new Error(`不是 ${EXPORT_FORMAT} 导出文件(format=${JSON.stringify(data?.format)})`);
52
+ if (data.version !== 1) throw new Error(`不支持的导出版本 ${JSON.stringify(data.version)}(支持 1)`);
53
+ if (!Array.isArray(data.exchanges)) throw new Error('导出文件缺少 exchanges 数组');
54
+ return data;
55
+ }
56
+
57
+ export const exportDoc = (exchanges) => ({ format: EXPORT_FORMAT, version: 1, exported_at: Date.now(), exchanges });
58
+
59
+ /** Local file name stamp: YYYYMMDD-HHMMSS. */
60
+ export function stamp(ms = Date.now()) {
61
+ const d = new Date(ms);
62
+ const p = (n) => String(n).padStart(2, '0');
63
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
64
+ }
package/ui/app.css ADDED
@@ -0,0 +1,375 @@
1
+ /* codeks-bridge inspector. Dense debug layout; colors follow prefers-color-scheme. */
2
+ :root {
3
+ --bg: #ffffff; --bg2: #f6f7f9; --bg3: #eaedf1; --fg: #1f2328; --fg2: #626b76;
4
+ --line: #d3d9e0; --line2: #eceff3; --accent: #0b67d0; --sel: #dcecff;
5
+ --ok: #1a7f37; --ok-bg: #dcf7e3; --bad: #c9222c; --bad-bg: #ffe8e6;
6
+ --warn: #8f5f00; --warn-bg: #fff3c4; --info: #0b67d0; --info-bg: #e2f0ff;
7
+ --sub: #7a45d6; --sub-bg: #f3eaff; --key: #8a3a00; --str: #0b3d78; --num: #0550ae; --bool: #9a3412;
8
+ --hue-l: 92%; --hue-s: 70%; --hue-fg-l: 28%;
9
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
10
+ --shadow: 0 6px 24px rgb(0 0 0 / 0.16);
11
+ color-scheme: light dark;
12
+ }
13
+ @media (prefers-color-scheme: dark) {
14
+ :root {
15
+ --bg: #0f1216; --bg2: #161a20; --bg3: #222830; --fg: #e3e8ee; --fg2: #8e98a4;
16
+ --line: #313843; --line2: #1f242b; --accent: #4c97f8; --sel: #1b3352;
17
+ --ok: #46c060; --ok-bg: #12291b; --bad: #f6635b; --bad-bg: #331517; --warn: #d9a43a; --warn-bg: #2e2410;
18
+ --info: #63aaff; --info-bg: #13283f; --sub: #bf95ff; --sub-bg: #281d3b;
19
+ --key: #f0a868; --str: #a8d4ff; --num: #82c2ff; --bool: #f3a07a;
20
+ --hue-l: 24%; --hue-s: 32%; --hue-fg-l: 82%;
21
+ --shadow: 0 6px 24px rgb(0 0 0 / 0.5);
22
+ }
23
+ }
24
+
25
+ * { box-sizing: border-box; }
26
+ [hidden] { display: none !important; }
27
+ html, body { height: 100%; }
28
+ body {
29
+ margin: 0; display: flex; flex-direction: column; overflow: hidden;
30
+ font: 13px/1.4 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Noto Sans CJK SC", sans-serif;
31
+ background: var(--bg); color: var(--fg);
32
+ }
33
+ button, input, select { font: inherit; color: inherit; }
34
+ input[type="search"], input[type="password"], input[type="text"], select {
35
+ background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 1px 6px; min-height: 22px;
36
+ }
37
+ input:focus-visible, select:focus-visible, button:focus-visible, .list:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
38
+ pre { margin: 0; }
39
+ table { border-collapse: collapse; }
40
+ h3, h4 { margin: 0; }
41
+
42
+ .mono, .tree, .raw, .sv-text, .ev-list, .diff-tree, .fields, .kv td { font-family: var(--mono); font-size: 12px; }
43
+ .dim { color: var(--fg2); }
44
+ .err { color: var(--bad); }
45
+ .grow { flex: 1 1 auto; }
46
+ .note { padding: 4px 8px; margin: 0 0 8px; background: var(--info-bg); border-left: 3px solid var(--info); border-radius: 3px; font-size: 12px; }
47
+ .empty { padding: 48px 24px; text-align: center; color: var(--fg2); }
48
+ .empty.small { padding: 14px 4px; text-align: left; }
49
+ .link { color: var(--accent); text-decoration: none; }
50
+ .link:hover { text-decoration: underline; }
51
+ .clickable { cursor: pointer; }
52
+ .check { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; cursor: pointer; white-space: nowrap; }
53
+ .str { color: var(--str); }
54
+ .num, .mono.number { color: var(--num); }
55
+ .bool, .mono.boolean { color: var(--bool); }
56
+ .null { color: var(--fg2); font-style: italic; }
57
+
58
+ /* ---------- buttons and chips ---------- */
59
+ .btn {
60
+ display: inline-flex; align-items: center; gap: 3px; padding: 1px 8px; min-height: 22px;
61
+ border: 1px solid var(--line); border-radius: 4px; background: var(--bg); cursor: pointer;
62
+ white-space: nowrap; font-size: 12px; line-height: 18px;
63
+ }
64
+ .btn:hover:not(:disabled) { background: var(--bg3); }
65
+ .btn:disabled { opacity: 0.45; cursor: default; }
66
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
67
+ .btn.primary:hover { filter: brightness(1.08); background: var(--accent); }
68
+ .btn.toggle.on { background: var(--info-bg); border-color: var(--info); color: var(--info); }
69
+ .btn.capture { font-weight: 600; color: var(--ok); border-color: var(--ok); }
70
+ .btn.capture.on { background: var(--bad); border-color: var(--bad); color: #fff; }
71
+ .btn.capture.on:hover { background: var(--bad); filter: brightness(1.1); }
72
+ .btn.more { display: flex; margin: 6px 0; }
73
+ .btn.close2 { color: var(--fg2); }
74
+ .sv-tools .btn, .tn-acts .btn, .list-head .btn, .ev-tools .btn, .pane-info .btn, .dn-row .btn, td .btn, .ann-detail .btn {
75
+ padding: 0 6px; min-height: 18px; font-size: 11px; line-height: 16px;
76
+ }
77
+
78
+ .chip {
79
+ display: inline-flex; align-items: center; gap: 3px; max-width: 46em; height: 18px; padding: 0 5px;
80
+ border: 1px solid var(--line); border-radius: 3px; background: var(--bg2); color: var(--fg);
81
+ font-size: 11px; line-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; flex: none;
82
+ }
83
+ .chip.sm { height: 16px; padding: 0 4px; font-size: 10.5px; line-height: 14px; }
84
+ .chip.ok { color: var(--ok); background: var(--ok-bg); border-color: transparent; }
85
+ .chip.bad { color: var(--bad); background: var(--bad-bg); border-color: transparent; }
86
+ .chip.warn { color: var(--warn); background: var(--warn-bg); border-color: transparent; }
87
+ .chip.info { color: var(--info); background: var(--info-bg); border-color: transparent; }
88
+ .chip.dim { color: var(--fg2); background: var(--bg3); border-color: transparent; }
89
+ .chip.sub { color: var(--sub); background: var(--sub-bg); border-color: transparent; }
90
+ .chip.id { font-family: var(--mono); }
91
+ .chip.kind { font-weight: 600; }
92
+ .chip.k-turn { color: var(--info); background: var(--info-bg); border-color: transparent; }
93
+ .chip.k-compaction { color: var(--sub); background: var(--sub-bg); border-color: transparent; }
94
+ .chip.k-invalid { color: var(--bad); background: var(--bad-bg); border-color: transparent; }
95
+ .chip.hue { color: hsl(var(--hue) 60% var(--hue-fg-l)); background: hsl(var(--hue) var(--hue-s) var(--hue-l)); border-color: transparent; font-weight: 600; }
96
+ .chip.phase { color: var(--warn); border-color: var(--warn); background: transparent; }
97
+ .chip.name { font-family: var(--mono); color: var(--key); background: transparent; }
98
+ .chip.parts { color: var(--fg2); background: transparent; border-style: dashed; }
99
+ .chip.cache { color: var(--ok); border-color: var(--ok); background: transparent; }
100
+ .chip.enc { color: var(--sub); border-color: var(--sub); background: transparent; font-family: var(--mono); }
101
+ .chip.ann { max-width: 60em; font-weight: 600; }
102
+ .chip.ann[title] { cursor: help; }
103
+ .chip.ann.click { cursor: pointer; }
104
+ .link-chip { cursor: pointer; color: var(--accent); background: transparent; }
105
+ .link-chip:hover { text-decoration: underline; }
106
+
107
+ /* ---------- top bar ---------- */
108
+ .top {
109
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 5px 10px;
110
+ border-bottom: 1px solid var(--line); background: var(--bg2); flex: none;
111
+ }
112
+ body.capturing .top { box-shadow: inset 0 3px 0 var(--bad); }
113
+ .brand { font-weight: 700; margin-right: 4px; }
114
+ .mem { display: inline-flex; align-items: center; gap: 5px; font-family: var(--mono); font-size: 11px; color: var(--fg2); white-space: nowrap; }
115
+ .mem meter { width: 70px; height: 10px; }
116
+ .filter { width: 280px; }
117
+ .chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
118
+ .pop { position: relative; }
119
+ .pop > summary { list-style: none; }
120
+ .pop > summary::-webkit-details-marker { display: none; }
121
+ .pop[open] > summary { background: var(--bg3); }
122
+ .pop-body {
123
+ position: absolute; top: calc(100% + 4px); left: 0; z-index: 30; min-width: 380px; max-width: 80vw; max-height: 60vh; overflow: auto;
124
+ padding: 6px; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; box-shadow: var(--shadow);
125
+ }
126
+ .pop-body table { width: 100%; font-size: 12px; }
127
+ .pop-body th, .pop-body td { text-align: left; padding: 2px 6px; border-bottom: 1px solid var(--line2); white-space: nowrap; }
128
+ .pop-body tbody tr { cursor: pointer; }
129
+ .pop-body tbody tr:hover { background: var(--bg2); }
130
+ .banner { padding: 5px 12px; background: var(--warn-bg); color: var(--warn); border-bottom: 1px solid var(--line); flex: none; font-size: 12px; }
131
+ .banner.bad { background: var(--bad-bg); color: var(--bad); }
132
+ .banner .btn { margin-left: 8px; }
133
+
134
+ /* ---------- layout ---------- */
135
+ .main { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: var(--list-w, 440px) 5px minmax(0, 1fr); }
136
+ .list-pane { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
137
+ .list-info { padding: 3px 10px; font-size: 11px; color: var(--fg2); border-bottom: 1px solid var(--line); flex: none; display: flex; gap: 8px; flex-wrap: wrap; }
138
+ .list { flex: 1 1 auto; overflow: auto; min-height: 0; }
139
+ .list:focus { outline: none; }
140
+ .splitter { background: var(--line2); border-left: 1px solid var(--line); cursor: col-resize; }
141
+ .splitter:hover, .splitter.drag { background: var(--accent); }
142
+ .detail { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
143
+
144
+ /* ---------- exchange list ---------- */
145
+ .xrow { padding: 4px 8px 4px 7px; border-bottom: 1px solid var(--line2); border-left: 3px solid transparent; cursor: pointer; }
146
+ .xrow.hued { border-left-color: hsl(var(--thue) 55% 55%); }
147
+ .xrow:hover { background: var(--bg2); }
148
+ .xrow.sel { background: var(--sel); }
149
+ .x1, .x2 { display: flex; align-items: center; gap: 5px; min-width: 0; }
150
+ .x2 { flex-wrap: wrap; margin-top: 2px; }
151
+ .x3 { margin-top: 2px; font-size: 11.5px; color: var(--fg2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
152
+ .x3.err { color: var(--bad); }
153
+ .x-time { font-family: var(--mono); font-size: 11.5px; color: var(--fg2); flex: none; }
154
+ .x-model { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
155
+ .x-model .dim { font-weight: 400; }
156
+ .x-dur { font-family: var(--mono); font-size: 11.5px; flex: none; min-width: 3.5em; text-align: right; }
157
+ .x-usage, .x-size { font-family: var(--mono); font-size: 11px; color: var(--fg2); white-space: nowrap; }
158
+ .x-size { margin-left: auto; }
159
+ .xrow.active .x-dur { color: var(--info); animation: pulse 1.2s ease-in-out infinite; }
160
+ @keyframes pulse { 50% { opacity: 0.35; } }
161
+ .tgroup {
162
+ position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; padding: 3px 8px;
163
+ background: var(--bg3); border-bottom: 1px solid var(--line); font-size: 12px; cursor: pointer; white-space: nowrap;
164
+ }
165
+ .tgroup > * { flex: none; }
166
+ .tgroup .dim { flex: 0 1 auto; min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
167
+ .swatch { width: 10px; height: 10px; border-radius: 2px; flex: none; background: var(--fg2); }
168
+ .tgroup.hued .swatch { background: hsl(var(--thue) 55% 55%); }
169
+
170
+ /* ---------- detail header, pipeline, panes ---------- */
171
+ .d-head { padding: 6px 10px; border-bottom: 1px solid var(--line); flex: none; }
172
+ .d-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
173
+ .d-line + .d-line { margin-top: 4px; }
174
+ .d-seq { font-weight: 700; }
175
+ .d-model { font-weight: 600; }
176
+ .d-actions { display: inline-flex; gap: 4px; }
177
+ .pipe { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; padding: 6px 10px; border-bottom: 1px solid var(--line); background: var(--bg2); flex: none; }
178
+ .card {
179
+ display: inline-flex; flex-direction: column; align-items: flex-start; gap: 1px; padding: 3px 9px; min-width: 112px;
180
+ border: 1px solid var(--line); border-left: 3px solid var(--card-c, var(--line)); border-radius: 5px;
181
+ background: var(--bg); cursor: pointer; text-align: left;
182
+ }
183
+ .card:hover { background: var(--bg3); }
184
+ .card.ok { --card-c: var(--ok); }
185
+ .card.bad { --card-c: var(--bad); }
186
+ .card.warn { --card-c: var(--warn); }
187
+ .card.info { --card-c: var(--info); }
188
+ .card.dim { --card-c: var(--fg2); }
189
+ .card.sel { background: var(--sel); border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
190
+ .card.sel2 { border-style: dashed; border-color: var(--accent); }
191
+ .card-main { font-size: 12px; font-weight: 600; white-space: nowrap; }
192
+ .card-sub { font-size: 11px; color: var(--fg2); font-family: var(--mono); white-space: nowrap; max-width: 22em; overflow: hidden; text-overflow: ellipsis; }
193
+ .card.narrow .card-sub { max-width: 19em; }
194
+ .pipe-side { display: flex; flex-direction: column; gap: 3px; }
195
+ .card.mini { min-width: 64px; padding: 1px 9px; }
196
+ .pipe-calls { display: flex; flex-direction: column; gap: 4px; }
197
+ .pipe-call { display: flex; align-items: center; gap: 5px; }
198
+ .arrow { color: var(--fg2); flex: none; }
199
+ .call-label { display: inline-block; padding: 0 6px; border-radius: 3px; font-size: 11px; line-height: 17px; white-space: nowrap; background: var(--bg3); color: var(--fg2); min-width: 8em; text-align: center; }
200
+ .call-label.l-main { background: var(--info-bg); color: var(--info); }
201
+ .call-label.l-summary { background: var(--warn-bg); color: var(--warn); }
202
+ .call-label.l-native { background: var(--sub-bg); color: var(--sub); }
203
+ .panes { flex: 1 1 auto; min-height: 0; display: flex; }
204
+ .pane { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
205
+ .panes.split .pane + .pane { border-left: 2px solid var(--line); }
206
+ .pane-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 4px 10px; border-bottom: 1px solid var(--line); flex: none; }
207
+ .pane-title { font-weight: 600; white-space: nowrap; }
208
+ .tabs { display: inline-flex; border: 1px solid var(--line); border-radius: 4px; overflow: hidden; }
209
+ .tab { border: 0; border-right: 1px solid var(--line); padding: 1px 10px; background: var(--bg); color: var(--fg2); cursor: pointer; font-size: 12px; }
210
+ .tab:last-child { border-right: 0; }
211
+ .tab:hover { background: var(--bg3); }
212
+ .tab.on { background: var(--accent); color: #fff; }
213
+ .pane-info { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 11.5px; min-width: 0; }
214
+ .pane-info .mono { max-width: 40em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
215
+ .pane-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 8px 10px 40px; }
216
+
217
+ /* ---------- JSON tree ---------- */
218
+ .tree { line-height: 1.45; }
219
+ .tn-row { padding: 0 2px; border-radius: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }
220
+ .tn-click { cursor: pointer; }
221
+ .tn-row:hover { background: var(--bg2); }
222
+ .tn-kids { margin-left: 5px; padding-left: 11px; border-left: 1px dotted var(--line); }
223
+ .tn-tog { display: inline-block; width: 12px; text-align: center; color: var(--fg2); user-select: none; flex: none; }
224
+ .tn-key { color: var(--key); }
225
+ .tn-idx { color: var(--fg2); }
226
+ .tn-sep { color: var(--fg2); }
227
+ .tn-sum { color: var(--fg2); margin-right: 6px; }
228
+ .tn-pv { color: var(--fg2); font-style: italic; }
229
+ .tn-acts { display: none; margin-left: 8px; gap: 3px; }
230
+ .tn-row:hover > .tn-acts { display: inline-flex; }
231
+ .tn-str { margin-left: 17px; }
232
+ .tn-row img.thumb, .item-row img.thumb { vertical-align: middle; }
233
+
234
+ /* ---------- long strings ---------- */
235
+ .sv { border: 1px solid var(--line); border-radius: 4px; margin: 2px 0 5px; background: var(--bg2); min-width: 0; }
236
+ .sv-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; padding: 2px 5px; border-bottom: 1px solid var(--line2); font-family: system-ui, sans-serif; font-size: 11px; }
237
+ .sv-text { padding: 4px 7px; white-space: pre-wrap; overflow-wrap: anywhere; max-height: 60vh; overflow: auto; }
238
+ .sv-omit { display: inline; padding: 0 4px; margin: 0 2px; border-radius: 3px; background: var(--warn-bg); color: var(--warn); cursor: pointer; font-style: italic; }
239
+ .sv-omit:hover { text-decoration: underline; }
240
+ .sv-extra:not(:empty) { padding: 4px 7px; border-top: 1px solid var(--line2); }
241
+ img.thumb { border: 1px solid var(--line); border-radius: 3px; background: repeating-conic-gradient(var(--bg3) 0 25%, var(--bg) 0 50%) 0 0 / 10px 10px; cursor: zoom-in; vertical-align: middle; }
242
+ img.thumb.sm { height: 28px; max-width: 64px; object-fit: contain; }
243
+ img.thumb.md { max-height: 120px; max-width: 240px; }
244
+ img.thumb.big { height: auto; max-height: 70vh; max-width: min(90%, 900px); cursor: zoom-out; }
245
+
246
+ /* ---------- structure view ---------- */
247
+ .struct { min-width: 0; }
248
+ table.fields { width: 100%; table-layout: auto; }
249
+ .fields th { text-align: left; font: 600 11px system-ui, sans-serif; color: var(--fg2); padding: 2px 6px; border-bottom: 1px solid var(--line); }
250
+ .fields td { padding: 2px 6px; border-bottom: 1px solid var(--line2); vertical-align: top; }
251
+ .fields tr.clickable:hover > td { background: var(--bg2); }
252
+ .fkey { white-space: nowrap; color: var(--key); width: 1%; }
253
+ .ftype { white-space: nowrap; color: var(--fg2); width: 1%; font-size: 11px; }
254
+ .fval { overflow-wrap: anywhere; }
255
+ .fval .chip { margin-left: 4px; }
256
+ tr.field-detail > td { background: var(--bg2); padding: 6px 8px 8px 22px; border-bottom: 1px solid var(--line); }
257
+ .item-list { margin-top: 14px; }
258
+ .list-head {
259
+ position: sticky; top: -8px; z-index: 3; display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
260
+ padding: 5px 0; background: var(--bg); border-bottom: 1px solid var(--line);
261
+ }
262
+ .list-title { font-family: var(--mono); font-weight: 700; }
263
+ .list-filter { width: 230px; }
264
+ .item { border-bottom: 1px solid var(--line2); }
265
+ .item-row { display: flex; align-items: center; gap: 6px; padding: 2px 4px; min-height: 24px; cursor: pointer; white-space: nowrap; overflow: hidden; }
266
+ .item-row:hover { background: var(--bg2); }
267
+ .item.open > .item-row { background: var(--bg3); }
268
+ .idx { font-family: var(--mono); font-size: 11.5px; color: var(--fg2); min-width: 3.2em; flex: none; }
269
+ .badges { display: inline-flex; gap: 3px; flex: none; }
270
+ .pv { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--fg2); font-family: var(--mono); font-size: 12px; }
271
+ .metrics { flex: none; color: var(--fg2); font-family: var(--mono); font-size: 11px; }
272
+ .item-body { padding: 4px 8px 8px 26px; background: var(--bg2); border-top: 1px solid var(--line2); }
273
+ .item-anns { padding: 4px 8px 4px 26px; display: grid; gap: 4px; }
274
+ .flash { animation: flash 1.8s ease-out; }
275
+ @keyframes flash { 0%, 30% { background: var(--warn-bg); box-shadow: inset 3px 0 0 var(--warn); } }
276
+
277
+ /* ---------- annotations, kv tables, overview ---------- */
278
+ .ann-detail { padding: 6px 8px; background: var(--bg2); border: 1px solid var(--line); border-radius: 4px; min-width: 0; }
279
+ .sub-title { font-weight: 600; font-size: 11.5px; color: var(--fg2); margin: 8px 0 3px; }
280
+ table.kv th { text-align: left; font-weight: 400; color: var(--fg2); padding: 2px 10px 2px 0; vertical-align: top; white-space: nowrap; }
281
+ table.kv td { padding: 2px 0; overflow-wrap: anywhere; vertical-align: top; }
282
+ table.kv.headers th { font-family: var(--mono); font-size: 12px; color: var(--key); }
283
+ table.kv.fields-kv { width: 100%; }
284
+ table.kv.fields-kv th { width: 1%; }
285
+ table.kv.fields-kv tr { border-bottom: 1px solid var(--line2); }
286
+ .hval { overflow-wrap: anywhere; }
287
+ .overview h4 { margin: 16px 0 5px; font-size: 12px; color: var(--fg2); }
288
+ .overview h4:first-child { margin-top: 0; }
289
+ table.calls, table.anns { width: 100%; font-size: 12px; }
290
+ .calls th, .calls td, .anns th, .anns td { text-align: left; padding: 3px 6px; border-bottom: 1px solid var(--line2); vertical-align: top; }
291
+ .calls th, .anns th { font-weight: 600; color: var(--fg2); border-bottom-color: var(--line); white-space: nowrap; }
292
+ .calls tr.clickable:hover > td, .anns tr.clickable:hover > td { background: var(--bg2); }
293
+ .anns tr.field-detail > td { padding: 4px 8px 10px; }
294
+ .timeline { display: grid; gap: 3px; max-width: 1100px; }
295
+ .tl-row { display: grid; grid-template-columns: 130px minmax(120px, 1fr) 270px; gap: 8px; align-items: center; padding: 1px 2px; border-radius: 3px; }
296
+ .tl-row:hover { background: var(--bg2); }
297
+ .tl-label { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
298
+ .tl-track { position: relative; height: 12px; background: var(--bg3); border-radius: 3px; overflow: hidden; }
299
+ .tl-bar { position: absolute; top: 2px; bottom: 2px; border-radius: 2px; background: var(--fg2); min-width: 2px; }
300
+ .tl-bar.ok { background: var(--ok); }
301
+ .tl-bar.bad { background: var(--bad); }
302
+ .tl-bar.warn { background: var(--warn); }
303
+ .tl-bar.info { background: var(--info); }
304
+ .tl-head { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--fg); opacity: 0.7; }
305
+ .tl-num { font-size: 11px; color: var(--fg2); white-space: nowrap; }
306
+ .tl-axis { font-size: 11px; margin-top: 2px; }
307
+
308
+ /* ---------- raw ---------- */
309
+ .rawbox .sv-tools { position: sticky; top: -8px; z-index: 2; background: var(--bg); border: 1px solid var(--line); border-radius: 4px 4px 0 0; }
310
+ .raw { padding: 6px 8px; white-space: pre-wrap; overflow-wrap: anywhere; background: var(--bg2); border: 1px solid var(--line); border-top: 0; border-radius: 0 0 4px 4px; }
311
+
312
+ /* ---------- events ---------- */
313
+ .ev-tools { position: sticky; top: -8px; z-index: 3; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 5px 0; background: var(--bg); border-bottom: 1px solid var(--line); }
314
+ .ev { border-bottom: 1px solid var(--line2); }
315
+ .ev.keep { opacity: 0.6; }
316
+ .ev-row { display: flex; align-items: center; gap: 8px; padding: 1px 4px; min-height: 21px; cursor: pointer; white-space: nowrap; overflow: hidden; }
317
+ .ev-row:hover { background: var(--bg2); }
318
+ .ev.group > .ev-row { background: color-mix(in srgb, var(--info-bg) 55%, transparent); }
319
+ .ev.group > .ev-row:hover { background: var(--info-bg); }
320
+ .ev-t { min-width: 8.5em; text-align: right; color: var(--fg2); flex: none; }
321
+ .ev-i { min-width: 6.5em; color: var(--fg2); flex: none; }
322
+ .ev-type { color: var(--accent); font-weight: 600; flex: none; }
323
+ .ev-type.err { color: var(--bad); }
324
+ .ev-body { padding: 4px 8px 8px 28px; background: var(--bg2); border-top: 1px solid var(--line2); font-family: system-ui, sans-serif; }
325
+ .ev-sub { margin-top: 4px; border-left: 2px solid var(--line); padding-left: 6px; }
326
+ .ev-body .ann-detail { margin-bottom: 6px; }
327
+
328
+ /* ---------- diff ---------- */
329
+ .diffpane { display: flex; flex-direction: column; gap: 8px; }
330
+ .diff-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
331
+ .diff-side { display: inline-flex; align-items: center; gap: 4px; padding: 3px 6px; border: 1px solid var(--line); border-radius: 5px; background: var(--bg2); }
332
+ .diff-side > b { font-family: var(--mono); }
333
+ .ex-sel { max-width: 380px; }
334
+ .diff-legend { font-size: 11.5px; color: var(--fg2); }
335
+ .diff-stats { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 4px 0 8px; }
336
+ .diff-tree { line-height: 1.45; }
337
+ .dn-row { padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-radius: 2px; }
338
+ .dn-row.tn-click:hover { background: var(--bg3); }
339
+ .sign { display: inline-block; width: 1.3em; font-weight: 700; color: var(--fg2); text-align: center; }
340
+ .dn.add > .dn-row { background: var(--ok-bg); }
341
+ .dn.add > .dn-row .sign { color: var(--ok); }
342
+ .dn.del > .dn-row { background: var(--bad-bg); }
343
+ .dn.del > .dn-row .sign { color: var(--bad); }
344
+ .dn.ch > .dn-row .sign, .dn.mod > .dn-row .sign { color: var(--warn); }
345
+ .dn.eq > .dn-row { color: var(--fg2); }
346
+ .dn .old { color: var(--bad); text-decoration: line-through; }
347
+ .dn .new { color: var(--ok); }
348
+ .dn-body { padding: 2px 0 4px 26px; }
349
+ .eqrun { padding: 0 2px 0 1.6em; color: var(--fg2); font-style: italic; cursor: pointer; border-radius: 2px; }
350
+ .eqrun:hover { background: var(--bg3); }
351
+ .sdiff { margin: 2px 0 6px 26px; }
352
+ .sdiff .sv-text { border: 1px solid var(--line); border-radius: 4px; background: var(--bg2); }
353
+ .sdiff .old { background: var(--bad-bg); color: var(--bad); text-decoration: line-through; }
354
+ .sdiff .new { background: var(--ok-bg); color: var(--ok); }
355
+ .sdiff .ctx { color: var(--fg2); }
356
+
357
+ /* ---------- overlays ---------- */
358
+ .toast {
359
+ position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 100; max-width: 80vw;
360
+ padding: 6px 14px; border-radius: 6px; background: var(--fg); color: var(--bg); box-shadow: var(--shadow);
361
+ opacity: 0; pointer-events: none; transition: opacity 0.2s;
362
+ }
363
+ .toast.show { opacity: 1; }
364
+ .toast.err { background: var(--bad); color: #fff; }
365
+ .drop {
366
+ position: fixed; inset: 12px; z-index: 90; display: flex; align-items: center; justify-content: center;
367
+ border: 3px dashed var(--accent); border-radius: 12px; background: color-mix(in srgb, var(--bg) 85%, transparent);
368
+ font-size: 20px; font-weight: 600; color: var(--accent); pointer-events: none;
369
+ }
370
+ .token-dialog { width: 380px; max-width: 92vw; padding: 16px 18px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--fg); box-shadow: var(--shadow); }
371
+ .token-dialog::backdrop { background: rgb(0 0 0 / 0.35); }
372
+ .token-dialog h3 { font-size: 15px; margin-bottom: 6px; }
373
+ .token-dialog p { margin: 6px 0; font-size: 12px; }
374
+ .token-dialog input { width: 100%; min-height: 28px; }
375
+ .dialog-buttons { display: flex; justify-content: flex-end; gap: 6px; margin-top: 12px; }