@openscout/scout 0.2.72 → 0.2.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +41 -7
- package/bin/scout +34 -0
- package/bin/scout.mjs +195 -18
- package/bin/scoutd +0 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/RepoDiffViewer-D8gg36W3.js +56 -0
- package/dist/client/assets/{addon-fit-C5ueiijZ.js → addon-fit-D4KdY-pj.js} +1 -1
- package/dist/client/assets/{addon-webgl-Cm7sl8RO.js → addon-webgl-Bo_tmKBp.js} +1 -1
- package/dist/client/assets/arc.es-D0gujIKy.js +188 -0
- package/dist/client/assets/embed-entry-CUVNwyB_.js +1 -0
- package/dist/client/assets/index-CRkjiso5.js +257 -0
- package/dist/client/assets/index-D_RX2cRb.css +1 -0
- package/dist/client/assets/{xterm-FFux-PdF.js → xterm-RfflDFod.js} +1 -1
- package/dist/client/favicon-16.png +0 -0
- package/dist/client/favicon-32.png +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/favicon.svg +48 -0
- package/dist/client/index.html +8 -2
- package/dist/client/openscout-icon.png +0 -0
- package/dist/client/site.webmanifest +19 -0
- package/dist/client/web-app-icon-192.png +0 -0
- package/dist/client/web-app-icon-512.png +0 -0
- package/dist/drizzle/0000_curly_iron_monger.sql +592 -0
- package/dist/drizzle/0001_invocation-status-columns.sql +7 -0
- package/dist/drizzle/0002_invocation-flight-metadata.sql +21 -0
- package/dist/drizzle/README.md +81 -0
- package/dist/drizzle/meta/0000_snapshot.json +4575 -0
- package/dist/drizzle/meta/0001_snapshot.json +4624 -0
- package/dist/drizzle/meta/0002_snapshot.json +4631 -0
- package/dist/drizzle/meta/_journal.json +27 -0
- package/dist/main.mjs +72583 -60970
- package/dist/node/main.mjs +7607 -0
- package/dist/openscout-terminal-relay.mjs +3819 -158
- package/dist/{pair-supervisor.mjs → pairing-runtime-controller.mjs} +51525 -46801
- package/dist/runtime/base-daemon.mjs +2153 -502
- package/dist/runtime/broker-daemon.mjs +34730 -27036
- package/dist/runtime/broker-process-manager.mjs +2038 -421
- package/dist/runtime/mesh-discover.mjs +2002 -420
- package/dist/scout-control-plane-web.mjs +54522 -39921
- package/dist/scout-web-server.mjs +54522 -39921
- package/dist/statusline.mjs +287 -0
- package/package.json +7 -3
- package/dist/client/assets/RepoDiffViewer-DRzXs-TP.js +0 -1
- package/dist/client/assets/index-CYs6TkOv.js +0 -191
- package/dist/client/assets/index-n_fpvwq8.css +0 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ../../apps/desktop/src/cli/commands/statusline.ts
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
|
|
5
|
+
// ../runtime/src/claude-statusline.ts
|
|
6
|
+
import { appendFile, mkdir, readFile, writeFile } from "fs/promises";
|
|
7
|
+
import { basename, dirname, join as join2 } from "path";
|
|
8
|
+
|
|
9
|
+
// ../runtime/src/support-paths.ts
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
var OPENSCOUT_RPC_CUTOVER_MARKER = "rpc-runtime-cutover-v1";
|
|
13
|
+
function resolveOpenScoutSupportPaths() {
|
|
14
|
+
const home = homedir();
|
|
15
|
+
const supportDirectory = process.env.OPENSCOUT_SUPPORT_DIRECTORY ?? join(home, "Library", "Application Support", "OpenScout");
|
|
16
|
+
const logsDirectory = join(supportDirectory, "logs");
|
|
17
|
+
const runtimeDirectory = join(supportDirectory, "runtime");
|
|
18
|
+
const catalogDirectory = join(supportDirectory, "catalog");
|
|
19
|
+
const controlHome = process.env.OPENSCOUT_CONTROL_HOME ?? join(home, ".openscout", "control-plane");
|
|
20
|
+
const knowledgeDirectory = join(controlHome, "knowledge");
|
|
21
|
+
return {
|
|
22
|
+
supportDirectory,
|
|
23
|
+
logsDirectory,
|
|
24
|
+
appLogsDirectory: join(logsDirectory, "app"),
|
|
25
|
+
brokerLogsDirectory: join(logsDirectory, "broker"),
|
|
26
|
+
runtimeDirectory,
|
|
27
|
+
catalogDirectory,
|
|
28
|
+
relayAgentsDirectory: join(runtimeDirectory, "agents"),
|
|
29
|
+
settingsPath: join(supportDirectory, "settings.json"),
|
|
30
|
+
harnessCatalogPath: join(catalogDirectory, "harness-catalog.json"),
|
|
31
|
+
relayAgentsRegistryPath: join(supportDirectory, "relay-agents.json"),
|
|
32
|
+
managedInstallsPath: join(supportDirectory, "managed-installs.json"),
|
|
33
|
+
hostInfoPath: join(supportDirectory, ".host-info"),
|
|
34
|
+
relayHubDirectory: process.env.OPENSCOUT_RELAY_HUB ?? join(home, ".openscout", "relay"),
|
|
35
|
+
controlHome,
|
|
36
|
+
knowledgeDirectory,
|
|
37
|
+
knowledgeQmdDirectory: join(knowledgeDirectory, "qmd"),
|
|
38
|
+
knowledgeSqlitePath: join(knowledgeDirectory, "knowledge.sqlite"),
|
|
39
|
+
desktopStatusPath: join(supportDirectory, "agent-status.json"),
|
|
40
|
+
workspaceStatePath: join(supportDirectory, "workspace-state.json"),
|
|
41
|
+
cutoverMarkerPath: join(supportDirectory, OPENSCOUT_RPC_CUTOVER_MARKER)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ../runtime/src/claude-statusline.ts
|
|
46
|
+
function resolveClaudeStatuslineDirectory() {
|
|
47
|
+
return join2(resolveOpenScoutSupportPaths().runtimeDirectory, "statusline");
|
|
48
|
+
}
|
|
49
|
+
function resolveClaudeStatuslineDelegatePath() {
|
|
50
|
+
return join2(resolveClaudeStatuslineDirectory(), "claude-delegate.json");
|
|
51
|
+
}
|
|
52
|
+
function resolveClaudeStatuslineWrapperPath() {
|
|
53
|
+
return join2(resolveClaudeStatuslineDirectory(), "claude-statusline.sh");
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
function stringValue(value) {
|
|
59
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
60
|
+
}
|
|
61
|
+
function numberValue(value) {
|
|
62
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
63
|
+
return;
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function percentLabel(value) {
|
|
67
|
+
const number = numberValue(value);
|
|
68
|
+
if (number === undefined)
|
|
69
|
+
return null;
|
|
70
|
+
if (Math.abs(number - Math.round(number)) < 0.05) {
|
|
71
|
+
return `${Math.round(number)}%`;
|
|
72
|
+
}
|
|
73
|
+
return `${number.toFixed(1)}%`;
|
|
74
|
+
}
|
|
75
|
+
function recordValue(value) {
|
|
76
|
+
return isRecord(value) ? value : null;
|
|
77
|
+
}
|
|
78
|
+
function parseClaudeStatuslinePayload(input) {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(input);
|
|
81
|
+
return isRecord(parsed) ? parsed : null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function normalizeClaudeStatuslineSnapshot(input, capturedAt = Date.now()) {
|
|
87
|
+
const workspace = recordValue(input.workspace);
|
|
88
|
+
const cwd = stringValue(input.cwd) ?? stringValue(workspace?.current_dir);
|
|
89
|
+
const snapshot = {
|
|
90
|
+
...input,
|
|
91
|
+
openscoutCapturedAt: numberValue(input.openscoutCapturedAt) ?? capturedAt
|
|
92
|
+
};
|
|
93
|
+
if (cwd && !stringValue(snapshot.cwd)) {
|
|
94
|
+
snapshot.cwd = cwd;
|
|
95
|
+
}
|
|
96
|
+
return snapshot;
|
|
97
|
+
}
|
|
98
|
+
async function captureClaudeStatuslineSnapshot(input, options = {}) {
|
|
99
|
+
const parsed = typeof input === "string" ? parseClaudeStatuslinePayload(input) : input;
|
|
100
|
+
if (!parsed) {
|
|
101
|
+
return { captured: false, reason: typeof input === "string" ? "invalid-json" : "invalid-record" };
|
|
102
|
+
}
|
|
103
|
+
if (!isRecord(parsed)) {
|
|
104
|
+
return { captured: false, reason: "invalid-record" };
|
|
105
|
+
}
|
|
106
|
+
const directory = options.directory ?? resolveClaudeStatuslineDirectory();
|
|
107
|
+
const latestPath = join2(directory, "claude-latest.json");
|
|
108
|
+
const historyPath = join2(directory, "claude-history.jsonl");
|
|
109
|
+
const snapshot = normalizeClaudeStatuslineSnapshot(parsed, options.capturedAt);
|
|
110
|
+
const line = JSON.stringify(snapshot);
|
|
111
|
+
await mkdir(directory, { recursive: true });
|
|
112
|
+
await Promise.all([
|
|
113
|
+
writeFile(latestPath, `${line}
|
|
114
|
+
`, "utf8"),
|
|
115
|
+
appendFile(historyPath, `${line}
|
|
116
|
+
`, "utf8")
|
|
117
|
+
]);
|
|
118
|
+
return {
|
|
119
|
+
captured: true,
|
|
120
|
+
latestPath,
|
|
121
|
+
historyPath,
|
|
122
|
+
snapshot
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function readClaudeStatuslineDelegate(path = resolveClaudeStatuslineDelegatePath()) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
128
|
+
if (!isRecord(parsed))
|
|
129
|
+
return null;
|
|
130
|
+
const command = stringValue(parsed.command);
|
|
131
|
+
if (!command)
|
|
132
|
+
return null;
|
|
133
|
+
return {
|
|
134
|
+
version: 1,
|
|
135
|
+
command,
|
|
136
|
+
source: parsed.source === "manual" ? "manual" : "claude-settings.statusLine",
|
|
137
|
+
installedAt: numberValue(parsed.installedAt) ?? Date.now(),
|
|
138
|
+
...isRecord(parsed.statusLine) ? { statusLine: parsed.statusLine } : {}
|
|
139
|
+
};
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function isOpenScoutClaudeStatuslineCommand(command, wrapperPath = resolveClaudeStatuslineWrapperPath()) {
|
|
145
|
+
const trimmed = command.trim();
|
|
146
|
+
if (!trimmed)
|
|
147
|
+
return false;
|
|
148
|
+
if (trimmed.includes(wrapperPath))
|
|
149
|
+
return true;
|
|
150
|
+
return /(?:^|\s|["'])\S*scout(?:["']|\s)+statusline\s+claude(?:\s|$)/u.test(trimmed);
|
|
151
|
+
}
|
|
152
|
+
function formatClaudeStatuslineFallback(snapshot) {
|
|
153
|
+
if (!snapshot) {
|
|
154
|
+
return "Scout | Claude status";
|
|
155
|
+
}
|
|
156
|
+
const model = recordValue(snapshot.model);
|
|
157
|
+
const workspace = recordValue(snapshot.workspace);
|
|
158
|
+
const context = recordValue(snapshot.context_window);
|
|
159
|
+
const rateLimits = recordValue(snapshot.rate_limits);
|
|
160
|
+
const fiveHour = recordValue(rateLimits?.five_hour);
|
|
161
|
+
const sevenDay = recordValue(rateLimits?.seven_day);
|
|
162
|
+
const cwd = stringValue(snapshot.cwd) ?? stringValue(workspace?.current_dir);
|
|
163
|
+
const parts = [
|
|
164
|
+
"Scout",
|
|
165
|
+
stringValue(model?.display_name) ?? stringValue(model?.id) ?? "Claude"
|
|
166
|
+
];
|
|
167
|
+
if (cwd) {
|
|
168
|
+
parts.push(basename(cwd));
|
|
169
|
+
}
|
|
170
|
+
const contextPercent = percentLabel(context?.used_percentage);
|
|
171
|
+
if (contextPercent) {
|
|
172
|
+
parts.push(`ctx ${contextPercent}`);
|
|
173
|
+
}
|
|
174
|
+
const fiveHourPercent = percentLabel(fiveHour?.used_percentage);
|
|
175
|
+
if (fiveHourPercent) {
|
|
176
|
+
parts.push(`5h ${fiveHourPercent}`);
|
|
177
|
+
}
|
|
178
|
+
const sevenDayPercent = percentLabel(sevenDay?.used_percentage);
|
|
179
|
+
if (sevenDayPercent) {
|
|
180
|
+
parts.push(`7d ${sevenDayPercent}`);
|
|
181
|
+
}
|
|
182
|
+
return parts.join(" | ");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ../../apps/desktop/src/cli/commands/statusline.ts
|
|
186
|
+
var DELEGATE_TIMEOUT_MS = 150;
|
|
187
|
+
var MAX_DELEGATE_OUTPUT_BYTES = 64 * 1024;
|
|
188
|
+
async function runStatuslineCommand(context, args) {
|
|
189
|
+
const [provider, ...rest] = args;
|
|
190
|
+
if (provider !== "claude") {
|
|
191
|
+
context.output.writeText("usage: scout statusline claude");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (process.stdin.isTTY) {
|
|
195
|
+
context.output.writeText("usage: scout statusline claude < statusline.json");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const delegateRequested = rest.includes("--delegate") || context.env.OPENSCOUT_STATUSLINE_RUN_DELEGATE === "1";
|
|
199
|
+
const noDelegate = rest.includes("--no-delegate") || context.env.OPENSCOUT_STATUSLINE_DELEGATE === "1";
|
|
200
|
+
const raw = await readStdin();
|
|
201
|
+
const snapshot = parseClaudeStatuslinePayload(raw);
|
|
202
|
+
try {
|
|
203
|
+
await captureClaudeStatuslineSnapshot(snapshot ?? raw);
|
|
204
|
+
} catch {}
|
|
205
|
+
if (delegateRequested && !noDelegate) {
|
|
206
|
+
const delegate = await readClaudeStatuslineDelegate();
|
|
207
|
+
if (delegate?.command && !isOpenScoutClaudeStatuslineCommand(delegate.command)) {
|
|
208
|
+
const delegated = await runDelegateStatusline(delegate.command, raw, context.env);
|
|
209
|
+
if (delegated) {
|
|
210
|
+
writeStatusline(delegated);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
writeStatusline(formatClaudeStatuslineFallback(snapshot));
|
|
216
|
+
}
|
|
217
|
+
async function readStdin() {
|
|
218
|
+
let out = "";
|
|
219
|
+
process.stdin.setEncoding("utf8");
|
|
220
|
+
for await (const chunk of process.stdin) {
|
|
221
|
+
out += typeof chunk === "string" ? chunk : String(chunk);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
function writeStatusline(value) {
|
|
226
|
+
const normalized = value.trimEnd();
|
|
227
|
+
process.stdout.write(`${normalized}
|
|
228
|
+
`);
|
|
229
|
+
}
|
|
230
|
+
function runDelegateStatusline(command, input, env) {
|
|
231
|
+
return new Promise((resolve) => {
|
|
232
|
+
const child = spawn("sh", ["-lc", command], {
|
|
233
|
+
env: {
|
|
234
|
+
...env,
|
|
235
|
+
OPENSCOUT_STATUSLINE_DELEGATE: "1"
|
|
236
|
+
},
|
|
237
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
238
|
+
});
|
|
239
|
+
let settled = false;
|
|
240
|
+
let stdout = "";
|
|
241
|
+
const done = (value) => {
|
|
242
|
+
if (settled)
|
|
243
|
+
return;
|
|
244
|
+
settled = true;
|
|
245
|
+
clearTimeout(timer);
|
|
246
|
+
resolve(value?.trimEnd() || null);
|
|
247
|
+
};
|
|
248
|
+
const timer = setTimeout(() => {
|
|
249
|
+
child.kill("SIGTERM");
|
|
250
|
+
done(null);
|
|
251
|
+
}, DELEGATE_TIMEOUT_MS);
|
|
252
|
+
child.stdout.setEncoding("utf8");
|
|
253
|
+
child.stdout.on("data", (chunk) => {
|
|
254
|
+
if (stdout.length >= MAX_DELEGATE_OUTPUT_BYTES)
|
|
255
|
+
return;
|
|
256
|
+
stdout += String(chunk).slice(0, MAX_DELEGATE_OUTPUT_BYTES - stdout.length);
|
|
257
|
+
});
|
|
258
|
+
child.once("error", () => done(null));
|
|
259
|
+
child.once("close", (code) => done(code === 0 ? stdout : null));
|
|
260
|
+
child.stdin.on("error", () => {
|
|
261
|
+
return;
|
|
262
|
+
});
|
|
263
|
+
child.stdin.end(input);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/statusline.ts
|
|
268
|
+
try {
|
|
269
|
+
const args = process.argv[2] === "statusline" ? process.argv.slice(3) : process.argv.slice(2);
|
|
270
|
+
const context = {
|
|
271
|
+
env: process.env,
|
|
272
|
+
output: {
|
|
273
|
+
writeText(value) {
|
|
274
|
+
process.stdout.write(`${value}
|
|
275
|
+
`);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
stderr(message) {
|
|
279
|
+
process.stderr.write(`${message}
|
|
280
|
+
`);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
await runStatuslineCommand(context, args);
|
|
284
|
+
} catch {
|
|
285
|
+
process.stdout.write(`Scout | Claude status
|
|
286
|
+
`);
|
|
287
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openscout/scout",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.73",
|
|
4
4
|
"description": "Public Scout package that installs the `scout` command and bundled local runtime",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/arach/openscout#readme",
|
|
16
16
|
"bin": {
|
|
17
|
-
"scout": "./bin/scout
|
|
17
|
+
"scout": "./bin/scout",
|
|
18
|
+
"openscout-runtime": "./bin/openscout-runtime.mjs"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"bun": ">=1.2"
|
|
18
22
|
},
|
|
19
23
|
"files": [
|
|
20
24
|
"bin",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as i,a as X,f as Z,j as e,b as M}from"./index-CYs6TkOv.js";const ee="1.2.7",F=`https://esm.sh/@pierre/diffs@${ee}`,U={base:`${F}?external=react,react-dom`,react:`${F}/react?external=react,react-dom`,worker:`${F}/worker/worker.js`},Y=["typescript","tsx","javascript","jsx","json","markdown","rust","swift","shellscript","yaml","css","html"];function re(){return import(U.react)}function te(){return import(U.base)}let b=null;function _(){return b||(b=(async()=>{const r="react",t="react-dom/client",[n,s,l,c]=await Promise.all([re(),te(),import(r),import(t)]),d=a=>typeof a=="function"||typeof a=="object"&&a!==null;if(!d(n?.CodeView)||!d(n?.WorkerPoolContextProvider)||typeof s?.parsePatchFiles!="function"||typeof l?.createElement!="function"||typeof c?.createRoot!="function")throw new Error(`Pierre loaded from ${F} but the expected exports are missing.`);const u=()=>{const a=`import ${JSON.stringify(U.worker)};`,f=URL.createObjectURL(new Blob([a],{type:"text/javascript"})),m=new Worker(f,{type:"module"});return URL.revokeObjectURL(f),m};return{CodeView:n.CodeView,PatchDiff:n.PatchDiff,WorkerPoolContextProvider:n.WorkerPoolContextProvider,parsePatchFiles:s.parsePatchFiles,preloadHighlighter:s.preloadHighlighter,workerFactory:u,createElement:l.createElement,createRoot:c.createRoot}})(),b.catch(()=>{b=null}),b)}let E=null;function se(r,t){return E||(E=r.preloadHighlighter({themes:[t],langs:[...Y]}).catch(()=>{E=null}),E)}function ne(r,t,n){return r.parsePatchFiles(t,n,!0)}function ae(r,t){const n=r.createRoot(t);let s=null;const l=c=>{s=c};return{render({items:c,theme:d,layout:u}){n.render(r.createElement(r.WorkerPoolContextProvider,{poolOptions:{workerFactory:r.workerFactory},highlighterOptions:{theme:d,langs:[...Y]}},r.createElement(r.CodeView,{className:"rd-codeview",items:c,options:{theme:d,diffStyle:u,stickyHeaders:!0},ref:l})))},scrollToItem(c,d="smooth"){s?.scrollTo({type:"item",id:c,align:"start",behavior:d})},unmount(){n.unmount()}}}const ie=["unstaged","staged"],G={unstaged:"Unstaged",staged:"Staged",branch:"Branch"},ce={added:"A",modified:"M",deleted:"D",renamed:"R",copied:"C",typechange:"T",conflict:"!",unknown:"?"};function de(r){return r.newPath??r.oldPath??"(unknown)"}function q(r){const t=r.split("/"),n=t[t.length-1]||r,s=t.slice(0,-1).join("/");return{name:n,dir:s}}function H(r,t){return`${r.oldPath??""}→${r.newPath??""}#${t}`}function le(r){const t=r.replace(/\/+$/,""),n=t.split("/");return n[n.length-1]||t}function ye({path:r,layers:t=ie,onClose:n,onOpenAsPage:s,className:l,title:c}){const d=t.join(","),u=i.useMemo(()=>d.split(","),[d]),[a,f]=i.useState(null),[m,v]=i.useState("loading"),[P,p]=i.useState(null),[x,j]=i.useState(null),[y,R]=i.useState(null),[S,N]=i.useState("loading"),[z,L]=i.useState(null),[C,B]=i.useState(null),[V,A]=i.useState(null),[I,O]=i.useState("split"),D=i.useCallback(async()=>{const o=X(r,u);p(null),o?(f(o.snapshot),j(W(o,!0)),v("ready")):(v("loading"),j(null));try{const h=await Z(r,u,{force:o!=null});f(h.snapshot),j(he(h,!1)),v("ready")}catch(h){const g=h instanceof Error?h.message:String(h);o?(j(W(o,!1,g)),v("ready")):(p(g),v("error"))}},[r,u]);i.useEffect(()=>{D()},[D]);const J=i.useCallback(()=>{N("loading"),L(null),_().then(o=>{R(o),N("ready")},o=>{L(o instanceof Error?o.message:String(o)),N("error")})},[]);i.useEffect(()=>{let o=!0;return _().then(h=>{o&&(R(h),N("ready"))},h=>{o&&(L(h instanceof Error?h.message:String(h)),N("error"))}),()=>{o=!1}},[]),i.useEffect(()=>{y&&a&&se(y,a.render.preferredTheme)},[y,a]),i.useEffect(()=>{a&&O(a.render.preferredLayout==="stacked"?"unified":"split")},[a]),i.useEffect(()=>{if(!a)return;const h=a.layers.filter(g=>g.files.length>0)[0]??a.layers[0]??null;B(g=>g&&a.layers.some(T=>T.kind===g)?g:h?h.kind:null)},[a]);const k=i.useMemo(()=>!a||!C?null:a.layers.find(o=>o.kind===C)??null,[a,C]);if(i.useEffect(()=>{if(!k){A(null);return}A(o=>{const h=k.files.map((g,T)=>H(g,T));return o&&h.includes(o)?o:h[0]??null})},[k]),m==="loading"&&!a)return e.jsx($,{className:l,children:e.jsxs(w,{children:[e.jsx("div",{className:"rd-spinner","aria-hidden":!0}),e.jsx("div",{className:"rd-center-title",children:"Reading worktree diff…"}),e.jsx("div",{className:"rd-center-body",children:r})]})});if(m==="error"&&!a)return e.jsx($,{className:l,children:e.jsxs(w,{children:[e.jsx("div",{className:"rd-center-title",children:"Couldn’t load the diff"}),e.jsx("div",{className:"rd-center-body",children:P??"The broker did not return a diff snapshot."}),e.jsx("div",{className:"rd-center-action",children:e.jsx("button",{type:"button",className:"rd-btn",onClick:()=>{D()},children:"Retry"})})]})});if(!a)return e.jsx($,{className:l});const Q=c??le(a.worktreePath);return e.jsxs($,{className:l,children:[e.jsx(ue,{heading:Q,worktreePath:a.worktreePath,snapshot:a,freshness:x,activeLayer:C,onLayer:B,layout:I,onLayout:O,onClose:n,onOpenAsPage:s}),e.jsx(xe,{snapshot:a}),e.jsxs("div",{className:"rd-body",children:[e.jsx(je,{layer:k,selectedFileKey:V,onSelect:A}),e.jsx(ve,{layer:k,selectedFileKey:V,renderKey:a.render.renderKey,theme:a.render.preferredTheme,layout:I,pierre:y,pierrePhase:S,pierreError:z,onRetryPierre:J})]})]})}function oe({pierre:r,items:t,theme:n,layout:s,scrollToItemId:l}){const c=i.useRef(null),d=i.useRef(null);return i.useEffect(()=>{if(!c.current)return;const u=ae(r,c.current);return d.current=u,()=>{d.current=null,setTimeout(()=>u.unmount(),0)}},[r]),i.useEffect(()=>{d.current?.render({items:t,theme:n,layout:s})},[t,n,s]),i.useEffect(()=>{if(!l)return;const u=requestAnimationFrame(()=>{d.current?.scrollToItem(l)});return()=>cancelAnimationFrame(u)},[l]),e.jsx("div",{ref:c,className:"rd-codeview-host"})}function $({className:r,children:t}){return e.jsx("div",{className:r?`rd-viewer ${r}`:"rd-viewer","data-scout-theme":!0,children:t})}function w({children:r}){return e.jsx("div",{className:"rd-center",children:e.jsx("div",{className:"rd-center-card",children:r})})}function ue({heading:r,worktreePath:t,snapshot:n,freshness:s,activeLayer:l,onLayer:c,layout:d,onLayout:u,onClose:a,onOpenAsPage:f}){const{dir:m}=q(t);return e.jsxs("div",{className:"rd-header",children:[e.jsxs("div",{className:"rd-header-id",children:[e.jsxs("div",{className:"rd-header-title",children:[e.jsx("span",{className:"rd-header-name",title:t,children:r}),m?e.jsx("span",{className:"rd-header-dir",children:m}):null]}),e.jsx("div",{className:"rd-header-sub",children:e.jsx(me,{layers:n.layers,activeLayer:l,onLayer:c})}),e.jsx(fe,{freshness:s,generatedAt:n.generatedAt})]}),e.jsxs("div",{className:"rd-header-actions",children:[e.jsxs("div",{className:"rd-segmented",role:"group","aria-label":"Diff layout",children:[e.jsx("button",{type:"button",className:d==="unified"?"on":"","aria-pressed":d==="unified",onClick:()=>u("unified"),children:"Unified"}),e.jsx("button",{type:"button",className:d==="split"?"on":"","aria-pressed":d==="split",onClick:()=>u("split"),children:"Split"})]}),f?e.jsx("button",{type:"button",className:"rd-close","aria-label":"Open diff as page",title:"Open as full page",onClick:f,children:"↗"}):null,a?e.jsx("button",{type:"button",className:"rd-close","aria-label":"Close",onClick:a,children:"✕"}):null]})]})}function W(r,t,n=null){return{fetchedAt:r.fetchedAt,cacheHit:!0,refreshing:t,refreshError:n}}function he(r,t){return{fetchedAt:r.fetchedAt,cacheHit:r.cacheHit,refreshing:t,refreshError:null}}function fe({freshness:r,generatedAt:t}){const n=r?.fetchedAt??t,s=Math.max(0,Date.now()-n),l=r?.cacheHit?`Prefetched ${M(s)}`:`Updated ${M(s)}`;return e.jsxs("div",{className:"rd-freshness",title:new Date(n).toLocaleString(),children:[e.jsx("span",{children:l}),r?.refreshing?e.jsx("span",{children:"Refreshing…"}):null,r?.refreshError?e.jsx("span",{children:"Refresh failed"}):null]})}function me({layers:r,activeLayer:t,onLayer:n}){return e.jsx("div",{className:"rd-layers",role:"tablist","aria-label":"Diff layers",children:r.map(s=>{const l=pe(s);return e.jsxs("button",{type:"button",role:"tab","aria-selected":s.kind===t,className:"rd-layer-tab"+(s.kind===t?" on":""),onClick:()=>n(s.kind),title:s.shortstat??void 0,children:[e.jsx("span",{children:G[s.kind]??s.kind}),e.jsx("span",{className:"rd-layer-stat",children:s.files.length===0?"clean":e.jsxs(e.Fragment,{children:[s.files.length,"f"," ",e.jsxs("span",{style:{color:"var(--rd-add)"},children:["+",l.add]})," ",e.jsxs("span",{style:{color:"var(--rd-del)"},children:["−",l.del]}),s.truncated?" ⚠":""]})})]},s.kind)})})}function pe(r){return r.files.reduce((t,n)=>({add:t.add+(n.additions??0),del:t.del+(n.deletions??0)}),{add:0,del:0})}function xe({snapshot:r}){const t=[];r.coverage.truncatedLayers>0&&t.push({level:"warning",text:`${r.coverage.truncatedLayers} layer${r.coverage.truncatedLayers===1?"":"s"} truncated — the native diff hit a size budget; some files or hunks are omitted.`}),r.coverage.scanBudgetReached&&t.push({level:"warning",text:"Scan budget reached — this diff is partial."});for(const n of r.diagnostics)t.push({level:n.level,text:n.path?`${n.message} (${n.path})`:n.message});return t.length===0?null:e.jsx(e.Fragment,{children:t.map((n,s)=>e.jsxs("div",{className:`rd-banner ${n.level}`,children:[e.jsx("span",{className:"rd-banner-dot","aria-hidden":!0}),e.jsx("span",{children:n.text})]},s))})}function je({layer:r,selectedFileKey:t,onSelect:n}){return e.jsxs("div",{className:"rd-rail",children:[e.jsx("div",{className:"rd-rail-head",children:r?`Changed files · ${r.files.length}`:"Changed files"}),e.jsx("div",{className:"rd-rail-list",children:r?.files.map((s,l)=>{const c=H(s,l),d=de(s),{name:u,dir:a}=q(d),f=(s.status==="renamed"||s.status==="copied")&&s.oldPath&&s.newPath&&s.oldPath!==s.newPath;return e.jsxs("button",{type:"button",className:"rd-file-row"+(c===t?" on":""),onClick:()=>n(c),title:f?`${s.oldPath} → ${s.newPath}`:d,children:[e.jsx("span",{className:`rd-file-glyph ${s.status}`,children:ce[s.status]}),e.jsxs("span",{className:"rd-file-id",children:[e.jsx("span",{className:"rd-file-name",children:u}),e.jsx("span",{className:"rd-file-dir",children:f?`${s.oldPath} → ${a||u}`:a}),e.jsx(ge,{file:s})]}),e.jsx("span",{className:"rd-file-churn",children:s.binary?e.jsx("span",{className:"add",style:{color:"var(--muted)"},children:"bin"}):e.jsxs(e.Fragment,{children:[e.jsxs("span",{className:"add",children:["+",s.additions??0]}),e.jsxs("span",{className:"del",children:["−",s.deletions??0]})]})})]},c)})})]})}function ge({file:r}){const t=[];return r.binary&&t.push("BINARY"),r.truncated&&t.push("TRUNCATED"),r.status==="conflict"&&t.push("CONFLICT"),t.length===0?null:e.jsx("span",{className:"rd-file-tags",children:t.map(n=>e.jsx("span",{className:"rd-file-tag",children:n},n))})}function ve({layer:r,selectedFileKey:t,renderKey:n,theme:s,layout:l,pierre:c,pierrePhase:d,pierreError:u,onRetryPierre:a}){const f=i.useMemo(()=>r?r.files.find((p,x)=>H(p,x)===t)??null:null,[r,t]),m=i.useMemo(()=>{if(!c||!r||!r.rawPatch)return null;try{const p=`${n}:${r.kind}:${r.patchOid}`,x=ne(c,r.rawPatch,p),j=[];return x.forEach((y,R)=>{y.files.forEach((S,N)=>{j.push({id:`${p}:${R}:${N}:${S.name}`,type:"diff",fileDiff:S})})}),{ok:!0,items:j,patches:x}}catch(p){return{ok:!1,error:p instanceof Error?p.message:String(p)}}},[c,r,n]),v=i.useMemo(()=>{if(!m||!m.ok||!f)return null;const p=[f.newPath,f.oldPath].filter(x=>!!x);for(const x of m.items){const j=x.fileDiff?.name;if(j&&p.includes(j))return x.id}return null},[m,f]);if(!r)return e.jsx("div",{className:"rd-surface",children:e.jsx(w,{children:e.jsx("div",{className:"rd-center-title",children:"No layer selected"})})});if(r.files.length===0)return e.jsx("div",{className:"rd-surface",children:e.jsxs(w,{children:[e.jsx("div",{className:"rd-center-title",children:"Nothing to diff"}),e.jsxs("div",{className:"rd-center-body",children:["The ",G[r.kind]??r.kind.toLowerCase()," layer is clean."]})]})});if(d==="loading")return e.jsx("div",{className:"rd-surface",children:e.jsxs(w,{children:[e.jsx("div",{className:"rd-spinner","aria-hidden":!0}),e.jsx("div",{className:"rd-center-title",children:"Loading the diff renderer…"}),e.jsx("div",{className:"rd-center-body",children:"Fetching Pierre Diffs + Shiki (first open only)."})]})});if(d==="error"||!c)return e.jsxs("div",{className:"rd-surface",children:[e.jsxs("div",{className:"rd-banner warning",children:[e.jsx("span",{className:"rd-banner-dot","aria-hidden":!0}),e.jsxs("span",{children:["The diff renderer failed to load",u?`: ${u}`:"."," Showing raw patch text."]})]}),e.jsx("div",{className:"rd-surface-scroll",children:e.jsx(K,{layer:r,onRetry:a})})]});if(!r.rawPatch||!m)return e.jsx("div",{className:"rd-surface",children:e.jsxs(w,{children:[e.jsx("div",{className:"rd-center-title",children:"No patch text available"}),e.jsx("div",{className:"rd-center-body",children:r.truncated?"This layer was truncated by the native diff budget; file summaries are shown in the rail.":"The native producer returned summaries without patch text for this layer."})]})});if(!m.ok)return e.jsxs("div",{className:"rd-surface",children:[e.jsxs("div",{className:"rd-banner warning",children:[e.jsx("span",{className:"rd-banner-dot","aria-hidden":!0}),e.jsxs("span",{children:["Couldn’t parse the patch (",m.error,"). Showing raw text."]})]}),e.jsx("div",{className:"rd-surface-scroll",children:e.jsx(K,{layer:r})})]});const P=r.files.filter(p=>p.truncated).length;return e.jsxs("div",{className:"rd-surface",children:[r.truncated||P>0?e.jsxs("div",{className:"rd-banner warning",children:[e.jsx("span",{className:"rd-banner-dot","aria-hidden":!0}),e.jsx("span",{children:r.truncated?"This layer was truncated; some files or hunks are omitted.":`${P} file${P===1?"":"s"} truncated.`})]}):null,e.jsx("div",{className:"rd-surface-scroll",children:e.jsx(oe,{pierre:c,items:m.items,theme:s,layout:l,scrollToItemId:v})})]})}function K({layer:r,onRetry:t}){return e.jsxs("div",{style:{padding:"var(--space-lg)"},children:[t?e.jsx("div",{className:"rd-center-action",style:{marginTop:0,marginBottom:"var(--space-md)"},children:e.jsx("button",{type:"button",className:"rd-btn",onClick:t,children:"Retry renderer"})}):null,e.jsx("pre",{style:{margin:0,whiteSpace:"pre",fontFamily:"var(--rd-mono)",fontSize:"var(--text-xs)",lineHeight:1.5,color:"var(--ink)"},children:r.rawPatch??"(no patch text)"})]})}export{ye as RepoDiffViewer,ye as default};
|