linkshell-cli 0.8.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +11 -6
  2. package/dist/cli/src/commands/gateway.d.ts +2 -0
  3. package/dist/cli/src/commands/gateway.js +29 -0
  4. package/dist/cli/src/commands/gateway.js.map +1 -0
  5. package/dist/cli/src/commands/host.d.ts +4 -0
  6. package/dist/cli/src/commands/host.js +12 -2
  7. package/dist/cli/src/commands/host.js.map +1 -1
  8. package/dist/cli/src/commands/screen.d.ts +10 -0
  9. package/dist/cli/src/commands/screen.js +82 -74
  10. package/dist/cli/src/commands/screen.js.map +1 -1
  11. package/dist/cli/src/commands/setup.d.ts +11 -1
  12. package/dist/cli/src/commands/setup.js +102 -38
  13. package/dist/cli/src/commands/setup.js.map +1 -1
  14. package/dist/cli/src/index.js +28 -338
  15. package/dist/cli/src/index.js.map +1 -1
  16. package/dist/cli/src/utils/daemon.d.ts +0 -7
  17. package/dist/cli/src/utils/daemon.js +1 -18
  18. package/dist/cli/src/utils/daemon.js.map +1 -1
  19. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  20. package/dist/gateway/src/accounts.d.ts +6 -0
  21. package/dist/gateway/src/accounts.js +32 -0
  22. package/dist/gateway/src/accounts.js.map +1 -0
  23. package/dist/gateway/src/index.d.ts +4 -0
  24. package/dist/gateway/src/index.js +5 -0
  25. package/dist/gateway/src/index.js.map +1 -0
  26. package/dist/gateway/src/main.d.ts +1 -0
  27. package/dist/gateway/src/main.js +57 -0
  28. package/dist/gateway/src/main.js.map +1 -0
  29. package/dist/gateway/src/rate-limit.d.ts +22 -0
  30. package/dist/gateway/src/rate-limit.js +66 -0
  31. package/dist/gateway/src/rate-limit.js.map +1 -0
  32. package/dist/gateway/src/relay.d.ts +84 -0
  33. package/dist/gateway/src/relay.js +434 -0
  34. package/dist/gateway/src/relay.js.map +1 -0
  35. package/dist/gateway/src/serve.d.ts +34 -0
  36. package/dist/gateway/src/serve.js +102 -0
  37. package/dist/gateway/src/serve.js.map +1 -0
  38. package/dist/gateway/src/store.d.ts +35 -0
  39. package/dist/gateway/src/store.js +109 -0
  40. package/dist/gateway/src/store.js.map +1 -0
  41. package/dist/gateway/src/subscription.d.ts +18 -0
  42. package/dist/gateway/src/subscription.js +46 -0
  43. package/dist/gateway/src/subscription.js.map +1 -0
  44. package/dist/host/src/drivers/claude/transcript.d.ts +23 -23
  45. package/dist/host/src/fs.d.ts +3 -3
  46. package/dist/host/src/input.d.ts +112 -27
  47. package/dist/host/src/input.js +214 -144
  48. package/dist/host/src/input.js.map +1 -1
  49. package/dist/host/src/rpc/server.js +1 -1
  50. package/dist/host/src/rpc/server.js.map +1 -1
  51. package/dist/host/src/screen-pacer.d.ts +25 -11
  52. package/dist/host/src/screen-pacer.js +28 -6
  53. package/dist/host/src/screen-pacer.js.map +1 -1
  54. package/dist/host/src/screen-viewer.d.ts +5 -0
  55. package/dist/host/src/screen-viewer.js +1231 -55
  56. package/dist/host/src/screen-viewer.js.map +1 -1
  57. package/dist/host/src/screen.d.ts +19 -10
  58. package/dist/host/src/screen.js +314 -108
  59. package/dist/host/src/screen.js.map +1 -1
  60. package/dist/host/src/terminals.d.ts +1 -1
  61. package/dist/host/src/terminals.js +1 -0
  62. package/dist/host/src/terminals.js.map +1 -1
  63. package/dist/wire/src/model.d.ts +6 -6
  64. package/dist/wire/src/relay.d.ts +8 -8
  65. package/dist/wire/src/rpc.d.ts +818 -818
  66. package/dist/wire/src/updates.d.ts +18 -18
  67. package/package.json +6 -15
  68. package/src/commands/gateway.ts +31 -0
  69. package/src/commands/host.ts +15 -2
  70. package/src/commands/screen.ts +78 -72
  71. package/src/commands/setup.ts +102 -60
  72. package/src/index.ts +29 -433
  73. package/src/utils/daemon.ts +2 -27
  74. package/dist/cli/src/config.d.ts +0 -14
  75. package/dist/cli/src/config.js +0 -23
  76. package/dist/cli/src/config.js.map +0 -1
  77. package/dist/cli/src/machine-id.d.ts +0 -6
  78. package/dist/cli/src/machine-id.js +0 -50
  79. package/dist/cli/src/machine-id.js.map +0 -1
  80. package/dist/cli/src/providers.d.ts +0 -13
  81. package/dist/cli/src/providers.js +0 -77
  82. package/dist/cli/src/providers.js.map +0 -1
  83. package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
  84. package/dist/cli/src/runtime/acp/acp-client.js +0 -430
  85. package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
  86. package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
  87. package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
  88. package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
  89. package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
  90. package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
  91. package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
  92. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
  93. package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
  94. package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
  95. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
  96. package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
  97. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
  98. package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
  99. package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
  100. package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
  101. package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
  102. package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
  103. package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
  104. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
  105. package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
  106. package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
  107. package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
  108. package/dist/cli/src/runtime/bridge-session.js +0 -1364
  109. package/dist/cli/src/runtime/bridge-session.js.map +0 -1
  110. package/dist/cli/src/runtime/hook-command.d.ts +0 -9
  111. package/dist/cli/src/runtime/hook-command.js +0 -80
  112. package/dist/cli/src/runtime/hook-command.js.map +0 -1
  113. package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
  114. package/dist/cli/src/runtime/screen-fallback.js +0 -202
  115. package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
  116. package/dist/cli/src/runtime/screen-share.d.ts +0 -34
  117. package/dist/cli/src/runtime/screen-share.js +0 -376
  118. package/dist/cli/src/runtime/screen-share.js.map +0 -1
  119. package/dist/cli/src/runtime/scrollback.d.ts +0 -11
  120. package/dist/cli/src/runtime/scrollback.js +0 -33
  121. package/dist/cli/src/runtime/scrollback.js.map +0 -1
  122. package/dist/cli/src/runtime/usage-report.d.ts +0 -34
  123. package/dist/cli/src/runtime/usage-report.js +0 -332
  124. package/dist/cli/src/runtime/usage-report.js.map +0 -1
  125. package/dist/cli/src/utils/keep-awake.d.ts +0 -6
  126. package/dist/cli/src/utils/keep-awake.js +0 -48
  127. package/dist/cli/src/utils/keep-awake.js.map +0 -1
  128. package/dist/cli/src/utils/lan-ip.d.ts +0 -9
  129. package/dist/cli/src/utils/lan-ip.js +0 -61
  130. package/dist/cli/src/utils/lan-ip.js.map +0 -1
  131. package/dist/host/src/input-helper.d.ts +0 -1
  132. package/dist/host/src/input-helper.js +0 -564
  133. package/dist/host/src/input-helper.js.map +0 -1
  134. package/dist/shared-protocol/src/index.d.ts +0 -17029
  135. package/dist/shared-protocol/src/index.js +0 -1064
  136. package/dist/shared-protocol/src/index.js.map +0 -1
  137. package/scripts/copy-web.mjs +0 -34
  138. package/src/config.ts +0 -36
  139. package/src/machine-id.ts +0 -61
  140. package/src/providers.ts +0 -97
  141. package/src/runtime/acp/acp-client.ts +0 -521
  142. package/src/runtime/acp/agent-process-discovery.ts +0 -308
  143. package/src/runtime/acp/agent-session.ts +0 -1186
  144. package/src/runtime/acp/agent-workspace.ts +0 -6590
  145. package/src/runtime/acp/claude-sdk-client.ts +0 -1221
  146. package/src/runtime/acp/claude-stream-json-client.ts +0 -633
  147. package/src/runtime/acp/json-rpc.ts +0 -184
  148. package/src/runtime/acp/provider-resolver.ts +0 -154
  149. package/src/runtime/bridge-session.ts +0 -1549
  150. package/src/runtime/hook-command.ts +0 -73
  151. package/src/runtime/screen-fallback.ts +0 -235
  152. package/src/runtime/screen-share.ts +0 -426
  153. package/src/runtime/scrollback.ts +0 -43
  154. package/src/runtime/usage-report.ts +0 -342
  155. package/src/utils/keep-awake.ts +0 -61
  156. package/src/utils/lan-ip.ts +0 -61
  157. package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
  158. package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
  159. package/web/assets/TerminalPanel-CTSklkct.js +0 -1
  160. package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
  161. package/web/assets/index-BsnDaeCt.css +0 -33
  162. package/web/assets/index-q85psYZ5.js +0 -36
  163. package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
  164. package/web/assets/vendor-react-M7xG3qiw.js +0 -49
  165. package/web/assets/vendor-xterm-D15THcI4.js +0 -9
  166. package/web/index.html +0 -37
  167. package/web/logos/claude.png +0 -0
  168. package/web/logos/codex.png +0 -0
  169. package/web/logos/copilot.png +0 -0
  170. package/web/logos/gemini.png +0 -0
  171. package/web/logos/linkshell-1024.png +0 -0
  172. package/web/logos/linkshell-splash.png +0 -0
  173. package/web/logos/linkshell.png +0 -0
@@ -1,342 +0,0 @@
1
- // Host-side usage aggregator. Scans local agent transcripts on disk and rolls
2
- // them up into the dimensions a usage dashboard needs (cumulative total, peak
3
- // day, activity heatmap by day, streaks, longest task). Fully DECOUPLED from any
4
- // live LinkShell turn — it reads what the agents already wrote to disk, so
5
- // history and externally-started sessions (any process, not just LinkShell) are
6
- // all counted, the same way ccusage / vibe-usage / CodeIsland work.
7
- //
8
- // Counting convention (matches ccusage): sum usage PER API CALL — context is
9
- // re-counted every turn, which is why totals reach billions. This is the usage
10
- // view, the opposite of the single-snapshot "current context occupancy" meter.
11
-
12
- import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
13
- import { homedir } from "node:os";
14
- import { join, basename } from "node:path";
15
-
16
- export interface UsageTotals {
17
- inputTokens: number;
18
- outputTokens: number;
19
- cacheReadTokens: number;
20
- cacheWriteTokens: number;
21
- totalTokens: number;
22
- calls: number;
23
- }
24
-
25
- export interface UsageBucket extends UsageTotals {
26
- key: string;
27
- }
28
-
29
- export interface UsageReport {
30
- totals: UsageTotals;
31
- sessions: number;
32
- /** Highest single-day token total. */
33
- peakDayTokens: number;
34
- /** Longest single session span (first→last activity), milliseconds. */
35
- longestTaskMs: number;
36
- /** Consecutive active days ending at the most recent active day. */
37
- currentStreakDays: number;
38
- /** Longest run of consecutive active days ever. */
39
- longestStreakDays: number;
40
- byDay: UsageBucket[]; // key = YYYY-MM-DD, ascending — drives the heatmap
41
- byHourWeekday: { weekday: number; hour: number; tokens: number }[]; // 0=Sun
42
- byTool: UsageBucket[]; // claude-code | codex
43
- byModel: UsageBucket[]; // claude-opus-4.8, gpt-5.5, ...
44
- byProject: UsageBucket[]; // cwd basename
45
- generatedAt: number;
46
- }
47
-
48
- function emptyTotals(): UsageTotals {
49
- return {
50
- inputTokens: 0,
51
- outputTokens: 0,
52
- cacheReadTokens: 0,
53
- cacheWriteTokens: 0,
54
- totalTokens: 0,
55
- calls: 0,
56
- };
57
- }
58
-
59
- // One call's worth of usage, tagged with its dimensions, fed into all buckets.
60
- interface CallSample {
61
- ts: number; // epoch ms
62
- sessionId: string; // for per-session duration
63
- tool: string; // claude-code | codex
64
- model: string;
65
- project: string; // cwd basename
66
- input: number;
67
- output: number;
68
- cacheRead: number;
69
- cacheWrite: number;
70
- }
71
-
72
- function dayKeyOf(ts: number): string {
73
- const d = new Date(ts);
74
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
75
- }
76
-
77
- class Accumulator {
78
- totals = emptyTotals();
79
- private day = new Map<string, UsageTotals>();
80
- private hw = new Map<string, number>(); // "weekday:hour" → tokens
81
- private tool = new Map<string, UsageTotals>();
82
- private model = new Map<string, UsageTotals>();
83
- private project = new Map<string, UsageTotals>();
84
- // Per-session activity timestamps, for longest-continuous-task duration.
85
- private sessionTimes = new Map<string, number[]>();
86
-
87
- private bump(map: Map<string, UsageTotals>, key: string, s: CallSample, tokens: number): void {
88
- let t = map.get(key);
89
- if (!t) { t = emptyTotals(); map.set(key, t); }
90
- t.inputTokens += s.input;
91
- t.outputTokens += s.output;
92
- t.cacheReadTokens += s.cacheRead;
93
- t.cacheWriteTokens += s.cacheWrite;
94
- t.totalTokens += tokens;
95
- t.calls += 1;
96
- }
97
-
98
- add(s: CallSample): void {
99
- const tokens = s.input + s.output + s.cacheRead + s.cacheWrite;
100
- if (tokens <= 0) return;
101
- this.totals.inputTokens += s.input;
102
- this.totals.outputTokens += s.output;
103
- this.totals.cacheReadTokens += s.cacheRead;
104
- this.totals.cacheWriteTokens += s.cacheWrite;
105
- this.totals.totalTokens += tokens;
106
- this.totals.calls += 1;
107
-
108
- this.bump(this.day, dayKeyOf(s.ts), s, tokens);
109
- const d = new Date(s.ts);
110
- const hwKey = `${d.getDay()}:${d.getHours()}`;
111
- this.hw.set(hwKey, (this.hw.get(hwKey) ?? 0) + tokens);
112
- this.bump(this.tool, s.tool, s, tokens);
113
- this.bump(this.model, s.model || "(unknown)", s, tokens);
114
- this.bump(this.project, s.project || "(unknown)", s, tokens);
115
-
116
- const times = this.sessionTimes.get(s.sessionId);
117
- if (!times) this.sessionTimes.set(s.sessionId, [s.ts]);
118
- else times.push(s.ts);
119
- }
120
-
121
- private toBuckets(map: Map<string, UsageTotals>): UsageBucket[] {
122
- return [...map.entries()]
123
- .map(([key, t]) => ({ key, ...t }))
124
- .sort((a, b) => b.totalTokens - a.totalTokens);
125
- }
126
-
127
- finish(sessions: number, generatedAt: number): UsageReport {
128
- const byDay = [...this.day.entries()]
129
- .map(([key, t]) => ({ key, ...t }))
130
- .sort((a, b) => a.key.localeCompare(b.key));
131
-
132
- const peakDayTokens = byDay.reduce((m, d) => Math.max(m, d.totalTokens), 0);
133
-
134
- // Longest CONTINUOUS work segment across all sessions. A Claude session
135
- // file can be resumed days later, so first→last would absurdly count idle
136
- // days. Instead, sort each session's call timestamps and split whenever the
137
- // gap exceeds IDLE_SPLIT_MS; the longest unbroken segment is the task length.
138
- const IDLE_SPLIT_MS = 30 * 60_000;
139
- let longestTaskMs = 0;
140
- for (const times of this.sessionTimes.values()) {
141
- if (times.length === 0) continue;
142
- times.sort((a, b) => a - b);
143
- let segStart = times[0]!;
144
- let prev = times[0]!;
145
- for (let i = 1; i < times.length; i++) {
146
- const cur = times[i]!;
147
- if (cur - prev > IDLE_SPLIT_MS) {
148
- longestTaskMs = Math.max(longestTaskMs, prev - segStart);
149
- segStart = cur;
150
- }
151
- prev = cur;
152
- }
153
- longestTaskMs = Math.max(longestTaskMs, prev - segStart);
154
- }
155
-
156
- const { currentStreakDays, longestStreakDays } = computeStreaks(byDay.map((d) => d.key), generatedAt);
157
-
158
- const byHourWeekday = [...this.hw.entries()].map(([k, tokens]) => {
159
- const parts = k.split(":");
160
- return { weekday: Number(parts[0]), hour: Number(parts[1]), tokens };
161
- });
162
-
163
- return {
164
- totals: this.totals,
165
- sessions,
166
- peakDayTokens,
167
- longestTaskMs,
168
- currentStreakDays,
169
- longestStreakDays,
170
- byDay,
171
- byHourWeekday,
172
- byTool: this.toBuckets(this.tool),
173
- byModel: this.toBuckets(this.model),
174
- byProject: this.toBuckets(this.project),
175
- generatedAt,
176
- };
177
- }
178
- }
179
-
180
- // Streaks over a sorted set of active day-keys (YYYY-MM-DD). Current streak
181
- // counts back from today (or yesterday — a gap of "today not yet active" still
182
- // keeps an unbroken run alive). Uses UTC-noon to step days, dodging DST edges.
183
- function computeStreaks(dayKeys: string[], now: number): { currentStreakDays: number; longestStreakDays: number } {
184
- if (dayKeys.length === 0) return { currentStreakDays: 0, longestStreakDays: 0 };
185
- const set = new Set(dayKeys);
186
- const dayMs = 86_400_000;
187
- const toKey = (ms: number) => dayKeyOf(ms);
188
-
189
- // Longest run.
190
- let longest = 1;
191
- let run = 1;
192
- const sorted = [...dayKeys].sort();
193
- for (let i = 1; i < sorted.length; i++) {
194
- const prev = new Date(sorted[i - 1] + "T12:00:00").getTime();
195
- const cur = new Date(sorted[i] + "T12:00:00").getTime();
196
- const gapDays = Math.round((cur - prev) / dayMs);
197
- if (gapDays === 1) run += 1;
198
- else run = 1;
199
- longest = Math.max(longest, run);
200
- }
201
-
202
- // Current run: walk backwards from today; if today isn't active, allow
203
- // starting from yesterday (today simply hasn't happened yet).
204
- let cursor = new Date(toKey(now) + "T12:00:00").getTime();
205
- if (!set.has(toKey(cursor))) cursor -= dayMs; // today inactive → start at yesterday
206
- let current = 0;
207
- while (set.has(toKey(cursor))) {
208
- current += 1;
209
- cursor -= dayMs;
210
- }
211
-
212
- return { currentStreakDays: current, longestStreakDays: longest };
213
- }
214
-
215
- function parseTs(value: unknown): number | undefined {
216
- if (typeof value === "number" && Number.isFinite(value)) {
217
- return value > 10_000_000_000 ? value : value * 1000;
218
- }
219
- if (typeof value === "string" && value.trim()) {
220
- const p = Date.parse(value);
221
- return Number.isNaN(p) ? undefined : p;
222
- }
223
- return undefined;
224
- }
225
-
226
- function walkJsonl(dir: string, onFile: (path: string) => void): void {
227
- let entries: string[];
228
- try { entries = readdirSync(dir); } catch { return; }
229
- for (const entry of entries) {
230
- const full = join(dir, entry);
231
- let st;
232
- try { st = statSync(full); } catch { continue; }
233
- if (st.isDirectory()) walkJsonl(full, onFile);
234
- else if (entry.endsWith(".jsonl")) onFile(full);
235
- }
236
- }
237
-
238
- // Claude: ~/.claude/projects/**/*.jsonl — each assistant line carries
239
- // message.usage. Skip <synthetic> (internal, no real usage).
240
- function ingestClaude(acc: Accumulator): number {
241
- const root = join(homedir(), ".claude", "projects");
242
- if (!existsSync(root)) return 0;
243
- let sessions = 0;
244
- walkJsonl(root, (path) => {
245
- sessions += 1;
246
- const sessionId = basename(path, ".jsonl");
247
- let text: string;
248
- try { text = readFileSync(path, "utf8"); } catch { return; }
249
- let fileCwd = "";
250
- for (const line of text.split(/\r?\n/)) {
251
- if (!line.trim()) continue;
252
- let o: any;
253
- try { o = JSON.parse(line); } catch { continue; }
254
- if (typeof o?.cwd === "string" && !fileCwd) fileCwd = o.cwd;
255
- if (o?.type !== "assistant") continue;
256
- const msg = o.message;
257
- const usage = msg?.usage;
258
- const model = typeof msg?.model === "string" ? msg.model : "";
259
- if (!usage || model === "<synthetic>" || !model) continue;
260
- const ts = parseTs(o.timestamp);
261
- if (ts === undefined) continue;
262
- acc.add({
263
- ts,
264
- sessionId,
265
- tool: "claude-code",
266
- model,
267
- project: basename(o.cwd || fileCwd || "(unknown)"),
268
- input: usage.input_tokens || 0,
269
- output: usage.output_tokens || 0,
270
- cacheRead: usage.cache_read_input_tokens || 0,
271
- cacheWrite: usage.cache_creation_input_tokens || 0,
272
- });
273
- }
274
- });
275
- return sessions;
276
- }
277
-
278
- // Codex: ~/.codex/sessions/**/rollout-*.jsonl — token_count events carry
279
- // info.last_token_usage (per-turn delta; total_token_usage is cumulative and
280
- // would double-count). cached_input_tokens is a subset of input_tokens.
281
- function ingestCodex(acc: Accumulator): number {
282
- const root = join(homedir(), ".codex", "sessions");
283
- if (!existsSync(root)) return 0;
284
- let sessions = 0;
285
- walkJsonl(root, (path) => {
286
- if (!basename(path).startsWith("rollout-")) return;
287
- sessions += 1;
288
- const sessionId = basename(path, ".jsonl");
289
- let text: string;
290
- try { text = readFileSync(path, "utf8"); } catch { return; }
291
- let model = "";
292
- let cwd = "";
293
- for (const line of text.split(/\r?\n/)) {
294
- if (!line.trim()) continue;
295
- let o: any;
296
- try { o = JSON.parse(line); } catch { continue; }
297
- const p = o?.payload ?? {};
298
- const type = p.type ?? o?.type;
299
- if (type === "session_meta") {
300
- if (typeof p.cwd === "string") cwd = p.cwd;
301
- if (typeof p.model === "string") model = p.model;
302
- continue;
303
- }
304
- if (type === "turn_context") {
305
- if (typeof p.model === "string") model = p.model;
306
- continue;
307
- }
308
- if (type !== "token_count") continue;
309
- const last = p.info?.last_token_usage;
310
- if (!last) continue;
311
- const ts = parseTs(o.timestamp);
312
- if (ts === undefined) continue;
313
- const cacheRead = last.cached_input_tokens || 0;
314
- const rawInput = last.input_tokens || 0;
315
- acc.add({
316
- ts,
317
- sessionId,
318
- tool: "codex",
319
- model: model || "gpt-5",
320
- project: basename(cwd || "(unknown)"),
321
- input: Math.max(0, rawInput - cacheRead),
322
- output: (last.output_tokens || 0) + (last.reasoning_output_tokens || 0),
323
- cacheRead,
324
- cacheWrite: 0,
325
- });
326
- }
327
- });
328
- return sessions;
329
- }
330
-
331
- let _cachedReport: UsageReport | null = null;
332
- let _cachedAt = 0;
333
-
334
- export function aggregateUsage(): UsageReport {
335
- const now = Date.now();
336
- if (_cachedReport && now - _cachedAt < 60_000) return _cachedReport;
337
- const acc = new Accumulator();
338
- const sessions = ingestClaude(acc) + ingestCodex(acc);
339
- _cachedReport = acc.finish(sessions, now);
340
- _cachedAt = now;
341
- return _cachedReport;
342
- }
@@ -1,61 +0,0 @@
1
- import { spawn, type ChildProcess } from "node:child_process";
2
- import { platform } from "node:os";
3
-
4
- export interface KeepAwakeHandle {
5
- enabled: boolean;
6
- stop: () => void;
7
- }
8
-
9
- export function shouldKeepAwake(optionEnabled: boolean | undefined): boolean {
10
- if (process.env.LINKSHELL_KEEP_AWAKE === "0") return false;
11
- if (platform() !== "darwin") return false;
12
- return optionEnabled !== false;
13
- }
14
-
15
- export function startKeepAwake(): KeepAwakeHandle {
16
- if (platform() !== "darwin") {
17
- return { enabled: false, stop: () => {} };
18
- }
19
-
20
- let child: ChildProcess | undefined;
21
- let stopping = false;
22
-
23
- try {
24
- child = spawn("caffeinate", ["-i", "-w", String(process.pid)], {
25
- stdio: "ignore",
26
- detached: false,
27
- });
28
- } catch (error) {
29
- process.stderr.write(
30
- `[bridge] keep-awake unavailable: ${error instanceof Error ? error.message : String(error)}\n`,
31
- );
32
- return { enabled: false, stop: () => {} };
33
- }
34
-
35
- child.on("error", (error) => {
36
- if (stopping) return;
37
- process.stderr.write(`[bridge] keep-awake unavailable: ${error.message}\n`);
38
- });
39
-
40
- child.on("exit", (code, signal) => {
41
- if (stopping) return;
42
- process.stderr.write(
43
- `[bridge] keep-awake stopped unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"})\n`,
44
- );
45
- });
46
-
47
- process.stderr.write(
48
- "[bridge] keep-awake enabled (macOS idle sleep prevention)\n",
49
- );
50
-
51
- return {
52
- enabled: true,
53
- stop: () => {
54
- stopping = true;
55
- if (child && !child.killed) {
56
- child.kill("SIGTERM");
57
- }
58
- child = undefined;
59
- },
60
- };
61
- }
@@ -1,61 +0,0 @@
1
- import { networkInterfaces } from "node:os";
2
-
3
- const IGNORED_INTERFACE_PATTERNS = [
4
- /^lo\d*$/i,
5
- /^bridge\d*$/i,
6
- /^utun\d*$/i,
7
- /^awdl\d*$/i,
8
- /^llw\d*$/i,
9
- /^vmnet\d*$/i,
10
- /^vboxnet\d*$/i,
11
- /^docker\d*$/i,
12
- /^br-/i,
13
- /^tun\d*$/i,
14
- /^tap\d*$/i,
15
- ];
16
-
17
- function isIgnoredInterface(name: string): boolean {
18
- return IGNORED_INTERFACE_PATTERNS.some((pattern) => pattern.test(name));
19
- }
20
-
21
- function isUsableIpv4(address: string): boolean {
22
- const parts = address.split(".").map((part) => Number(part));
23
- if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
24
- return false;
25
- }
26
- const [a, b] = parts;
27
- if (a === 0 || a === 127 || a === 169 && b === 254) return false;
28
- if (a === 198 && (b === 18 || b === 19)) return false;
29
- return true;
30
- }
31
-
32
- /**
33
- * Get the best non-internal IPv4 address (LAN IP).
34
- *
35
- * macOS often exposes virtual bridge/VPN interfaces before Wi-Fi/Ethernet,
36
- * which can produce QR codes that phones cannot reach. Prefer real LAN
37
- * interfaces and ignore virtual point-to-point bridges.
38
- * Falls back to 127.0.0.1 if none found.
39
- */
40
- export function getLanIp(): string {
41
- const nets = networkInterfaces();
42
- const candidates: Array<{ address: string; score: number }> = [];
43
-
44
- for (const name of Object.keys(nets)) {
45
- if (isIgnoredInterface(name)) continue;
46
- const addrs = nets[name];
47
- if (!addrs) continue;
48
- for (const addr of addrs) {
49
- if (addr.family !== "IPv4" || addr.internal || !isUsableIpv4(addr.address)) continue;
50
- const score =
51
- /^en\d+$/i.test(name) ? 100 :
52
- /^(eth|wlan|wifi|wi-fi)/i.test(name) ? 90 :
53
- 10;
54
- candidates.push({ address: addr.address, score });
55
- }
56
- }
57
-
58
- candidates.sort((a, b) => b.score - a.score);
59
- if (candidates[0]) return candidates[0].address;
60
- return "127.0.0.1";
61
- }
@@ -1,2 +0,0 @@
1
- import{r as n,j as e}from"./vendor-react-M7xG3qiw.js";import{k as b,a as w,l as k,h as C,m as z}from"./index-q85psYZ5.js";import"./vendor-highlight-NXWAAyIv.js";function I({store:x,initialPath:d,onClose:j}){const[c,u]=n.useState(d||"."),[y,N]=n.useState([]),[o,l]=n.useState(!1),[f,r]=n.useState(null),[i,p]=n.useState(null),a=n.useCallback(async t=>{l(!0),r(null),p(null);try{const s=await x.browse(t,!0);s.error?r(s.error):(u(s.path),N([...s.entries].sort((m,h)=>m.isDirectory!==h.isDirectory?m.isDirectory?-1:1:m.name.localeCompare(h.name))))}catch(s){r(s.message||"浏览失败")}finally{l(!1)}},[x]);n.useEffect(()=>{a(d||".")},[a,d]);const g=async t=>{l(!0),r(null);try{const s=await x.readFile(t.path);s.error?r(s.error):p({path:s.path,content:s.content,truncated:s.truncated})}catch(s){r(s.message||"读取失败")}finally{l(!1)}},v=c.replace(/\/+$/,"").split("/").slice(0,-1).join("/")||"/";return e.jsxs("div",{className:"flex h-full flex-col bg-surface",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-2xs text-content-secondary",children:[e.jsx(b,{size:13,className:"text-content-faint"})," 文件"]}),e.jsx("button",{onClick:j,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭文件面板",children:e.jsx(w,{size:15})})]}),e.jsxs("div",{className:"flex items-center gap-1 border-b border-border px-2 py-1.5",children:[e.jsx("button",{onClick:()=>a(v),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"上级目录","aria-label":"上级目录",children:e.jsx(k,{size:14})}),e.jsx("input",{value:c,onChange:t=>u(t.target.value),onKeyDown:t=>t.key==="Enter"&&a(c),className:"flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none"}),e.jsx("button",{onClick:()=>a(c),disabled:o,className:"cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"刷新","aria-label":"刷新",children:e.jsx(C,{size:14})})]}),f&&e.jsx("p",{className:"px-3 py-1.5 text-2xs text-danger",children:f}),e.jsxs("div",{className:"min-h-0 flex-1 overflow-auto",children:[o&&e.jsx("p",{className:"px-3 py-2 text-2xs text-content-muted",children:"加载中…"}),!o&&i?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-3 py-1.5",children:[e.jsx("span",{className:"truncate font-mono text-2xs text-content-secondary",children:i.path}),e.jsx("button",{onClick:()=>p(null),className:"codex-btn-ghost text-2xs",children:"返回"})]}),e.jsxs("pre",{className:"whitespace-pre-wrap px-3 py-2 font-mono text-2xs leading-relaxed text-content-secondary",children:[i.content,i.truncated&&e.jsx("span",{className:"text-warning",children:`
2
- …(已截断)`})]})]}):!o&&y.map(t=>e.jsxs("button",{onClick:()=>t.isDirectory?a(t.path):g(t),className:"flex w-full cursor-pointer items-center gap-2 px-3 py-1 text-left transition-colors hover:bg-surface-overlay",children:[t.isDirectory?e.jsx(b,{size:13,className:"shrink-0 text-content-faint"}):e.jsx(z,{size:13,className:"shrink-0 text-content-faint"}),e.jsx("span",{className:"truncate font-mono text-2xs text-content-primary",children:t.name}),!t.isDirectory&&t.size!=null&&e.jsxs("span",{className:"ml-auto font-mono text-2xs text-content-faint",children:[t.size,"B"]})]},t.path))]})]})}export{I as FileBrowser};
@@ -1,2 +0,0 @@
1
- import{r,j as e}from"./vendor-react-M7xG3qiw.js";import{e as me,g as he,a as be,c as ge,d as ve,f as je,h as ye,i as Ne,j as ke}from"./index-q85psYZ5.js";import"./vendor-highlight-NXWAAyIv.js";const Z=[{cssProp:"color",label:"文本颜色"},{cssProp:"background-color",label:"背景"},{cssProp:"font-size",label:"字号"},{cssProp:"font-weight",label:"字重"},{cssProp:"padding",label:"内边距"},{cssProp:"border-radius",label:"圆角"}];function we(d){var i;const c=(i=d.ownerDocument.defaultView)==null?void 0:i.getComputedStyle(d),o={};for(const{cssProp:f}of Z)o[f]=(c==null?void 0:c.getPropertyValue(f).trim())??"";return o}function $e(d){const c=/^\s*(\d{1,5})(.*)$/.exec(d);if(!c)return null;const o=c[1];if(Number(o)<1||Number(o)>65535)return null;let i=c[2]||"/";return i.startsWith("/")||(i=`/${i}`),{port:o,path:i}}function Ce(d){if(d.id)return`#${CSS.escape(d.id)}`;const c=[];let o=d,i=0;for(;o&&o.nodeType===1&&i<4;){let f=o.tagName.toLowerCase();const P=(o.getAttribute("class")||"").trim().split(/\s+/).filter(Boolean).slice(0,2).map(m=>`.${CSS.escape(m)}`).join("");f+=P;const h=o.parentElement;if(h){const m=[...h.children].filter(y=>y.tagName===o.tagName);m.length>1&&(f+=`:nth-of-type(${m.indexOf(o)+1})`)}if(c.unshift(f),o.id){c[0]=`#${CSS.escape(o.id)}`;break}o=h,i+=1}return c.join(" > ")}function Me({gatewayUrl:d,sessionId:c,initialAuthToken:o,defaultPort:i,isMobile:f,onClose:P,onAnnotate:h}){const[m,y]=r.useState(i||"3000"),[u,ee]=r.useState(null),[R,te]=r.useState(o??null),[E,T]=r.useState(!1),[M,H]=r.useState("desktop"),[ne,U]=r.useState(0),[I,N]=r.useState(!1),[K,z]=r.useState(null),[v,j]=r.useState(null),[l,L]=r.useState(null),[W,k]=r.useState(""),[w,B]=r.useState(!1),[$,D]=r.useState({}),[C,S]=r.useState({}),A=r.useRef(null),V=r.useRef(null),O=r.useMemo(()=>me(),[]);r.useEffect(()=>{let t=!1;return T(!1),he().then(n=>{t||te((n==null?void 0:n.accessToken)??null)}).finally(()=>{t||T(!0)}),()=>{t=!0}},[o]);const b=r.useMemo(()=>{if(!u||o&&!E)return null;const t=`${d.replace(/\/+$/,"")}/tunnel/${encodeURIComponent(c)}/${u.port}${u.path}`,n=[];if(O&&n.push(`token=${encodeURIComponent(O)}`),R&&n.push(`auth_token=${encodeURIComponent(R)}`),n.length===0)return t;const s=u.path.includes("?")?"&":"?";return`${t}${s}${n.join("&")}`},[u,d,c,O,R,E]),_=()=>{const t=$e(m);t&&(ee(t),U(n=>n+1))},se=()=>U(t=>t+1),oe=()=>{var t;if(u)try{const n=(t=A.current)==null?void 0:t.contentWindow;if(!n)return;const s=`/tunnel/${encodeURIComponent(c)}/${u.port}`;let x=n.location.pathname;x.startsWith(s)&&(x=x.slice(s.length)||"/");const p=new URLSearchParams(n.location.search);p.delete("token"),p.delete("auth_token");const g=p.toString();y(`${u.port}${x}${g?`?${g}`:""}`)}catch{}},F=(t,n)=>{const s=A.current,x=s==null?void 0:s.contentDocument;if(!s||!x)return null;const p=s.getBoundingClientRect();return x.elementFromPoint(t-p.left,n-p.top)},X=t=>{const n=t.getBoundingClientRect();return{top:n.top,left:n.left,width:n.width,height:n.height}},re=t=>{try{const n=F(t.clientX,t.clientY);j(n?X(n):null)}catch{j(null),z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},le=t=>{t.preventDefault();try{const n=F(t.clientX,t.clientY);if(!n||!("style"in n))return;const s=n;L({el:s,selector:Ce(s),text:(s.textContent||"").replace(/\s+/g," ").trim().slice(0,120),rect:X(s)}),D(we(s)),S({}),k("")}catch{z("此页面跨域,无法读取元素(生产环境同源可用)"),N(!1)}},ce=(t,n)=>{if(l){try{l.el.style.setProperty(t,n)}catch{}S(s=>({...s,[t]:n}))}},ae=()=>Object.entries(C).filter(([t,n])=>n!==($[t]??"")).map(([t,n])=>({cssProp:t,from:$[t]??"",to:n})),Y=()=>{if(!l)return;const t=ae(),n=["关于预览页面中的这个元素:",`- 选择器: \`${l.selector}\``,l.text?`- 文本: "${l.text}"`:"",`- 路径: localhost:${m}`,W.trim()?`- 批注: ${W.trim()}`:""];if(t.length>0){n.push("- 请把以下样式改动应用到源码:");for(const s of t)n.push(` - ${s.cssProp}: ${s.from||"(默认)"} → ${s.to}`)}h==null||h(n.filter(Boolean).join(`
2
- `)),L(null),k(""),D({}),S({}),B(!1),j(null),N(!1)},q=()=>{L(null),k(""),D({}),S({}),B(!1)},ie=()=>{z(null),q(),j(null),N(t=>!t)};return e.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[e.jsxs("div",{className:"flex items-center gap-1.5 border-b border-border bg-surface px-2 py-1.5",children:[f&&e.jsx("button",{onClick:P,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭预览",children:e.jsx(be,{size:15})}),e.jsxs("div",{className:"flex min-w-0 flex-1 items-center gap-1 rounded-lg border border-border bg-surface-raised px-2 py-1",children:[e.jsx("span",{className:"shrink-0 font-mono text-2xs text-content-faint",children:"localhost:"}),e.jsx("input",{value:m,onChange:t=>y(t.target.value),onKeyDown:t=>t.key==="Enter"&&_(),placeholder:"3000",spellCheck:!1,className:"min-w-0 flex-1 bg-transparent font-mono text-2xs text-content-primary outline-none","aria-label":"端口与路径"}),e.jsx("button",{onClick:_,className:"shrink-0 cursor-pointer rounded bg-accent-dim px-2 py-0.5 text-2xs font-medium text-white transition-colors hover:bg-accent",children:"前往"})]}),e.jsx("button",{onClick:ie,disabled:!u,className:`shrink-0 cursor-pointer rounded-md p-1.5 transition-colors disabled:opacity-40 ${I?"bg-accent-dim text-white":"text-content-muted hover:bg-surface-overlay hover:text-accent"}`,title:"标注元素","aria-label":"标注元素",children:e.jsx(ge,{size:14})}),e.jsxs("div",{className:"flex shrink-0 items-center rounded-lg border border-border bg-surface-raised p-0.5",children:[e.jsx("button",{onClick:()=>H("desktop"),className:`cursor-pointer rounded p-1 transition-colors ${M==="desktop"?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-primary"}`,title:"桌面视口","aria-label":"桌面视口",children:e.jsx(ve,{size:13})}),e.jsx("button",{onClick:()=>H("mobile"),className:`cursor-pointer rounded p-1 transition-colors ${M==="mobile"?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-primary"}`,title:"移动视口","aria-label":"移动视口",children:e.jsx(je,{size:13})})]}),e.jsx("button",{onClick:se,disabled:!u,className:"shrink-0 cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent disabled:opacity-40",title:"刷新","aria-label":"刷新",children:e.jsx(ye,{size:14})}),e.jsx("button",{onClick:()=>b&&window.open(b,"_blank","noopener,noreferrer"),disabled:!b,className:"shrink-0 cursor-pointer rounded-md p-1.5 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent disabled:opacity-40",title:"在新窗口打开","aria-label":"在新窗口打开",children:e.jsx(Ne,{size:14})})]}),K&&e.jsx("p",{className:"border-b border-border bg-warning/10 px-3 py-1.5 text-2xs text-warning",children:K}),I&&!l&&e.jsx("p",{className:"border-b border-border bg-accent/10 px-3 py-1.5 text-2xs text-accent",children:"标注模式:移动鼠标高亮元素,点击选择并添加批注。"}),e.jsx("div",{className:"min-h-0 flex-1 overflow-auto bg-surface-raised",children:b?e.jsx("div",{className:M==="mobile"?"mx-auto h-full w-[390px] max-w-full border-x border-border":"h-full w-full",children:e.jsxs("div",{className:"relative h-full w-full",children:[e.jsx("iframe",{ref:A,src:b,title:"端口预览",onLoad:oe,className:"h-full w-full bg-white"},`${b}-${ne}`),I&&e.jsx("div",{className:"absolute inset-0 cursor-crosshair",onMouseMove:re,onMouseLeave:()=>j(null),onClick:le,children:v&&!l&&e.jsx("div",{className:"pointer-events-none absolute border-2 border-accent bg-accent/10",style:{top:v.top,left:v.left,width:v.width,height:v.height}})}),l&&(()=>{var J,Q;const s=((J=V.current)==null?void 0:J.clientHeight)??600,x=l.rect.top+l.rect.height+8,p=l.rect.top-8,g=w?340:140,G=x+g>s&&p-g>0?Math.max(4,p-g):x,de=Math.max(4,Math.min(l.rect.left,(((Q=V.current)==null?void 0:Q.clientWidth)??800)-280-4));return e.jsxs("div",{className:"absolute z-20 flex flex-col gap-2 rounded-xl border border-accent-dim/40 bg-surface-raised px-3 py-2.5 shadow-2xl animate-fade-in",style:{top:G,left:de,width:280,maxHeight:s-G-8},children:[e.jsx("p",{className:"truncate font-mono text-2xs text-content-faint",title:l.selector,children:l.selector}),l.text&&e.jsxs("p",{className:"-mt-1 truncate text-2xs text-content-muted",children:['"',l.text,'"']}),e.jsx("input",{value:W,onChange:a=>k(a.target.value),onKeyDown:a=>a.key==="Enter"&&Y(),autoFocus:!0,placeholder:"添加批注…",className:"w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-xs text-content-primary placeholder-content-muted outline-none focus:border-accent-dim"}),e.jsxs("button",{onClick:()=>B(a=>!a),className:`flex cursor-pointer items-center gap-1 self-start rounded-md px-1.5 py-0.5 text-2xs transition-colors ${w?"text-accent":"text-content-muted hover:text-content-secondary"}`,children:[e.jsx("svg",{width:10,height:10,viewBox:"0 0 10 10",className:`shrink-0 transition-transform ${w?"rotate-180":""}`,children:e.jsx("path",{d:"M2 3.5 L5 6.5 L8 3.5",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round"})}),"调整样式"]}),w&&e.jsx("div",{className:"grid grid-cols-2 gap-x-2 gap-y-1.5 animate-fade-in",children:Z.map(({cssProp:a,label:ue})=>{const xe=C[a]??$[a]??"",pe=a in C&&C[a]!==($[a]??"");return e.jsxs("label",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:`text-2xs ${pe?"text-accent":"text-content-faint"}`,children:ue}),e.jsx("input",{value:xe,onChange:fe=>ce(a,fe.target.value),spellCheck:!1,className:"w-full rounded-md border border-border bg-surface px-2 py-1 font-mono text-2xs text-content-primary outline-none focus:border-accent-dim"})]},a)})}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{onClick:q,className:"codex-btn-ghost flex-1 text-2xs",children:"取消"}),e.jsx("button",{onClick:Y,className:"codex-btn-primary flex-1 text-2xs",children:"发送给 Agent"})]})]})})()]})}):e.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 px-6 text-center",children:[e.jsx(ke,{size:34,className:"text-content-faint"}),e.jsx("p",{className:"text-sm leading-6 text-content-muted",children:u&&!E?"正在准备预览认证...":"输入主机上服务的端口号,预览远程本地服务"})]})})]})}export{Me as PortPreview};
@@ -1 +0,0 @@
1
- import{r as l,j as i}from"./vendor-react-M7xG3qiw.js";import{x as w,a as C}from"./vendor-xterm-D15THcI4.js";import{I as T,a as A,b as B}from"./index-q85psYZ5.js";import"./vendor-highlight-NXWAAyIv.js";function x(e,t){const r=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return r?`rgb(${r})`:t}function $(e,t,r){const u=getComputedStyle(document.documentElement).getPropertyValue(e).trim();return u?`rgb(${u} / ${t})`:r}function E(){return{background:x("--c-canvas","#0b0d0f"),foreground:x("--c-content-primary","#e6e8eb"),cursor:x("--c-accent","#2dd4bf"),selectionBackground:$("--c-accent",.22,"rgba(45,212,191,0.25)"),black:x("--c-content-faint","#4e535a"),brightBlack:x("--c-content-muted","#70767e"),white:x("--c-content-secondary","#9ea5ad"),brightWhite:x("--c-content-primary","#e9ebee")}}function F(e,t){const r=l.useRef(null),u=l.useRef(null),h=l.useRef(null);return l.useEffect(()=>{if(!r.current||!e)return;const n=new w.Terminal({fontFamily:"JetBrains Mono, ui-monospace, monospace",fontSize:13,cursorBlink:!0,theme:E(),scrollback:5e3}),f=new C.FitAddon;n.loadAddon(f),n.open(r.current),f.fit(),u.current=n,h.current=f;const p=e.terminalBuffer(t);p&&n.write(p);const v=n.onData(c=>e.sendInput(t,c)),b=n.onResize(({cols:c,rows:o})=>e.sendResize(t,c,o)),k=e.onEvent(c=>{if(c.type==="terminal.reset"&&c.terminalId===t){n.reset();return}c.type==="terminal.output"&&c.terminalId===t&&n.write(c.data)}),y=new ResizeObserver(()=>{try{f.fit(),e.sendResize(t,n.cols,n.rows)}catch{}});y.observe(r.current);const s=new MutationObserver(()=>{n.options.theme=E()});return s.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),e.sendResize(t,n.cols,n.rows),()=>{v.dispose(),b.dispose(),k(),y.disconnect(),s.disconnect(),n.dispose(),u.current=null,h.current=null}},[e,t]),{containerRef:r}}function I(e,t){return e==="default"?"默认":t||e.slice(0,8)}const z=e=>`linkshell:web:lastTerminal:${e}`;function L(e){if(!e)return null;try{return localStorage.getItem(z(e))}catch{return null}}function P(e,t){if(e)try{localStorage.setItem(z(e),t)}catch{}}function O(e,t){if(e.length===0)return null;if(t&&e.includes(t))return t;const r=e.filter(u=>u!=="default");return r.length>0?r[r.length-1]:e[0]}function V({bridge:e,terminalId:t}){const{containerRef:r}=F(e,t);return i.jsx("div",{ref:r,className:"xterm-host min-h-0 flex-1 bg-canvas p-2"})}function N({bridge:e,onNewTerminal:t}){const r=e==null?void 0:e.sessionId,[u,h]=l.useState([]),[n,f]=l.useState("default"),p=l.useRef(!1),v=l.useRef(!1),b=l.useCallback((s,c)=>{h(o=>o.some(m=>m.id===s)?o:[...o,{id:s,label:I(s,c)}])},[]);l.useEffect(()=>{if(!e)return;p.current=!1,v.current=!1;const s=L(r);return e.requestTerminalList(),e.onEvent(o=>{if(o.type==="terminal.output"){b(o.terminalId);return}if(o.type==="terminal.list"){const m=o.envelope,a=m.payload;if(m.type==="terminal.spawned"){const R=a==null?void 0:a.terminalId;R&&(b(R,a==null?void 0:a.projectName),f(R));return}if(m.type==="terminal.list"){const j=(Array.isArray(a==null?void 0:a.terminals)?a.terminals:[]).filter(d=>typeof d.terminalId=="string"&&d.status!=="exited").map(d=>({id:d.terminalId,label:I(d.terminalId,d.projectName)}));if(h(j),!p.current){p.current=!0;const d=j.map(S=>S.id),g=O(d,s);g?f(g):v.current||(v.current=!0,t==null||t())}}}})},[e,b,t]),l.useEffect(()=>{p.current&&n&&P(r,n)},[r,n]);const k=()=>t==null?void 0:t(),y=s=>{e==null||e.killTerminal(s),h(c=>{var m;const o=c.filter(a=>a.id!==s);return s===n&&f(((m=o[o.length-1])==null?void 0:m.id)??"default"),o})};return i.jsxs("div",{className:"flex h-full flex-col bg-canvas",children:[i.jsxs("div",{className:"flex items-center gap-0.5 border-b border-border bg-surface-raised px-1.5 py-1",children:[i.jsx(T,{size:13,className:"mx-1 shrink-0 text-content-faint"}),i.jsx("div",{className:"flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto",children:u.map(s=>i.jsxs("div",{className:`group flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-2xs transition-colors ${s.id===n?"bg-surface-overlay text-content-primary":"text-content-muted hover:bg-surface-overlay/60"}`,children:[i.jsx("button",{onClick:()=>f(s.id),className:"cursor-pointer font-mono",children:s.label}),u.length>1&&i.jsx("button",{onClick:()=>y(s.id),className:"cursor-pointer rounded text-content-faint opacity-0 transition hover:text-danger group-hover:opacity-100","aria-label":"关闭终端",children:i.jsx(A,{size:11})})]},s.id))}),i.jsx("button",{onClick:k,className:"ml-1 shrink-0 cursor-pointer rounded-md p-1 text-content-muted transition-colors hover:bg-surface-overlay hover:text-accent",title:"新建终端","aria-label":"新建终端",children:i.jsx(B,{size:14})})]}),i.jsx(V,{bridge:e,terminalId:n},n)]})}export{N as TerminalPanel};
@@ -1 +0,0 @@
1
- import{j as e,r as T}from"./vendor-react-M7xG3qiw.js";import{a as $}from"./index-q85psYZ5.js";import"./vendor-highlight-NXWAAyIv.js";function h(t){return t>=1e8?(t/1e8).toFixed(1)+"亿":t>=1e4?(t/1e4).toFixed(1)+"万":String(Math.round(t))}function S(t){const a=Math.round(t/6e4),n=Math.floor(a/60),l=a%60;return n>0?`${n}小时${l}分`:`${l}分`}const C=53,j=864e5;function k(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}function E(t,a){const n=new Map;for(const o of t)n.set(o.key,o.totalTokens);const l=new Date;l.setHours(12,0,0,0);const s=new Date(l.getTime()-(C*7-1)*j);s.setDate(s.getDate()-s.getDay());const r=new Map;if(a==="weekly")for(const[o,d]of n){const i=new Date(o+"T12:00:00");i.setDate(i.getDate()-i.getDay());const x=k(i);r.set(x,(r.get(x)??0)+d)}const c=[],u=[];let N=0,w=0,D=-1;for(let o=new Date(s);o<=l;w++){const d=[];for(let x=0;x<7;x++){const p=new Date(o.getTime()+x*j),M=k(p),y=n.get(M)??0;a==="cumulative"&&(N+=y);let g=y;if(a==="weekly"){const f=new Date(p);f.setDate(f.getDate()-f.getDay()),g=r.get(k(f))??0}else a==="cumulative"&&(g=N);d.push({date:p,key:M,value:g,daily:y})}const i=d[0].date.getMonth();i!==D&&(u.push({col:w,label:`${i+1}月`}),D=i),c.push(d),o=new Date(o.getTime()+7*j)}let b=0;for(const o of c)for(const d of o)b=Math.max(b,d.value);return{weeks:c,max:b,monthLabels:u}}function F(t,a){if(t<=0||a<=0)return{className:"bg-surface-raised"};const n=t/a;return{className:"",style:{backgroundColor:`rgba(96,165,250,${n>.66?1:n>.33?.72:n>.1?.48:.28})`}}}function m({value:t,label:a}){return e.jsxs("div",{className:"flex flex-col items-center justify-center gap-1 px-4 py-3",children:[e.jsx("span",{className:"whitespace-nowrap font-mono text-xl font-semibold text-content-primary tabular-nums",children:t}),e.jsx("span",{className:"text-2xs text-content-muted",children:a})]})}function v({title:t,buckets:a}){const n=a.slice(0,6),l=n.reduce((s,r)=>Math.max(s,r.totalTokens),0);return n.length===0?null:e.jsxs("div",{className:"rounded-xl border border-border bg-surface p-4",children:[e.jsx("p",{className:"mb-3 text-2xs font-semibold uppercase tracking-wider text-content-faint",children:t}),e.jsx("div",{className:"space-y-2",children:n.map(s=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"w-28 shrink-0 truncate text-xs text-content-secondary",title:s.key,children:s.key}),e.jsx("div",{className:"relative h-2 flex-1 overflow-hidden rounded-full bg-surface-raised",children:e.jsx("div",{className:"absolute inset-y-0 left-0 rounded-full",style:{width:`${l>0?s.totalTokens/l*100:0}%`,backgroundColor:"rgba(96,165,250,0.7)"}})}),e.jsx("span",{className:"w-14 shrink-0 text-right font-mono text-2xs tabular-nums text-content-muted",children:h(s.totalTokens)})]},s.key))})]})}function z({report:t}){const[a,n]=T.useState("daily"),l=T.useMemo(()=>t?E(t.byDay,a):null,[t,a]);return t?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"grid grid-cols-2 divide-x divide-border rounded-2xl border border-border bg-surface sm:grid-cols-5",children:[e.jsx(m,{value:h(t.totals.totalTokens),label:"累计 Token 数"}),e.jsx(m,{value:h(t.peakDayTokens),label:"峰值 Token 数"}),e.jsx(m,{value:S(t.longestTaskMs),label:"最长任务时长"}),e.jsx(m,{value:`${t.currentStreakDays} 天`,label:"当前连续天数"}),e.jsx(m,{value:`${t.longestStreakDays} 天`,label:"最长连续天数"})]}),e.jsxs("div",{className:"rounded-2xl border border-border bg-surface p-5",children:[e.jsxs("div",{className:"mb-4 flex items-center justify-between",children:[e.jsx("h3",{className:"text-sm font-semibold text-content-primary",children:"Token 活动"}),e.jsx("div",{className:"flex items-center gap-1 text-2xs",children:[["daily","每日"],["weekly","每周"],["cumulative","累计"]].map(([s,r])=>e.jsx("button",{onClick:()=>n(s),className:`rounded-md px-2 py-1 transition-colors ${a===s?"bg-surface-overlay text-content-primary":"text-content-muted hover:text-content-secondary"}`,children:r},s))})]}),l&&e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("div",{className:"inline-flex flex-col gap-1",children:[e.jsx("div",{className:"flex gap-1",children:l.weeks.map((s,r)=>e.jsx("div",{className:"flex flex-col gap-1",children:s.map(c=>{const u=F(c.value,l.max);return e.jsx("div",{className:`h-3 w-3 rounded-[3px] ${u.className}`,style:u.style,title:`${c.key} · ${h(c.daily)} tokens`},c.key)})},r))}),e.jsx("div",{className:"relative mt-1.5 h-4 text-2xs text-content-faint",children:l.monthLabels.map(s=>e.jsx("span",{className:"absolute",style:{left:`${s.col*16}px`},children:s.label},s.col))})]})})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-3",children:[e.jsx(v,{title:"工具分布",buckets:t.byTool}),e.jsx(v,{title:"模型分布",buckets:t.byModel}),e.jsx(v,{title:"项目分布",buckets:t.byProject})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-12 text-content-muted",children:[e.jsx("span",{className:"h-6 w-6 animate-spin rounded-full border-2 border-content-faint border-t-accent"}),e.jsx("p",{className:"text-sm",children:"正在扫描本机会话记录…"})]})}function L({report:t,onClose:a}){return e.jsxs("div",{className:"fixed inset-0 z-50 flex flex-col bg-canvas/95 backdrop-blur-sm",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-border px-5 py-3",children:[e.jsx("h2",{className:"text-base font-semibold text-content-primary",children:"用量统计"}),e.jsx("button",{onClick:a,className:"codex-btn-ghost px-2 py-1.5","aria-label":"关闭",children:e.jsx($,{size:16})})]}),e.jsx("div",{className:"mx-auto w-full max-w-5xl flex-1 overflow-y-auto px-5 py-6",children:e.jsx(z,{report:t})})]})}export{L as UsageDashboard,z as UsageDashboardContent};