@pharaoh-so/mcp 0.3.52 → 0.3.53
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/.mcp.json +1 -1
- package/CHANGELOG.md +10 -0
- package/dist/index.js +11 -3
- package/dist/install-skills.js +2 -2
- package/dist/setup.d.ts +19 -0
- package/dist/setup.js +45 -11
- package/package.json +1 -1
- package/trace-app.html +299 -112
package/.mcp.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to `@pharaoh-so/mcp` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.53] - 2026-10-23
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- The Trace card is a compact receipt: the finding with its counts, a verdict such as risk, and one bar showing where the results sit (callers by module, dead or possibly unused, matches by module, code by module). The evidence opens on Details. A single-tool card is about 80px tall instead of 290 to 2,200px.
|
|
12
|
+
- While a tool runs, the card says what Pharaoh is doing ("Tracing callers of parseEvent…"). Caveats such as an incomplete graph or a stale map stay visible while the evidence is closed.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- With no `claude` CLI on PATH, `npx @pharaoh-so/mcp` stops before sign-in and says why: setup registers Pharaoh through that CLI, and the Claude desktop app's built-in Claude Code isn't on PATH. The message gives the CLI installer. It used to link the desktop app download, write the slash commands anyway and end with "Pharaoh is ready".
|
|
16
|
+
- When `claude mcp add` fails, setup ends without "Pharaoh is ready" and names the `claude mcp add` command to run by hand.
|
|
17
|
+
|
|
8
18
|
## [0.3.52] - 2026-10-23
|
|
9
19
|
|
|
10
20
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -80,6 +80,13 @@ async function main() {
|
|
|
80
80
|
// Running `npx @pharaoh-so/mcp` is the only command a user needs.
|
|
81
81
|
if (isInteractive) {
|
|
82
82
|
process.stderr.write(`${BANNER}\n`);
|
|
83
|
+
// Setup registers everything through the `claude` CLI on PATH. Without it,
|
|
84
|
+
// stop before sign-in: nothing is written and saved credentials stay.
|
|
85
|
+
const { claudeCliMissing, hasClaude, runSetup } = await import("./setup.js");
|
|
86
|
+
if (!hasClaude()) {
|
|
87
|
+
printLines(...claudeCliMissing());
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
83
90
|
// Always re-authenticate for a fresh session
|
|
84
91
|
deleteCredentials();
|
|
85
92
|
const deviceCode = await requestDeviceCode(server);
|
|
@@ -92,10 +99,11 @@ async function main() {
|
|
|
92
99
|
const newCreds = tokenToCredentials(token, sseUrl);
|
|
93
100
|
writeCredentials(newCreds);
|
|
94
101
|
printAuthSuccess(token.github_login ?? null, token.tenant_name ?? null, token.repos?.length ?? 0);
|
|
95
|
-
// Register MCP server + install skills (silent — user doesn't need internals)
|
|
102
|
+
// Register MCP server + install skills (silent — user doesn't need internals).
|
|
103
|
+
// runSetup has printed the failure and the command to run by hand; no success line.
|
|
104
|
+
if (!runSetup({ silent: true }))
|
|
105
|
+
process.exit(1);
|
|
96
106
|
process.stderr.write(" Setting up...");
|
|
97
|
-
const { runSetup } = await import("./setup.js");
|
|
98
|
-
runSetup({ silent: true });
|
|
99
107
|
const { keptCommands } = runInstallSkills(undefined, { silent: true });
|
|
100
108
|
process.stderr.write(" done.\n");
|
|
101
109
|
// Two things the user must hear about: their settings.json (the only
|
package/dist/install-skills.js
CHANGED
|
@@ -11,7 +11,7 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { ensurePharaohHome } from "./credentials.js";
|
|
14
|
-
import { claudeCliFor } from "./setup.js";
|
|
14
|
+
import { CLAUDE_CLI_SETUP_URL, claudeCliFor } from "./setup.js";
|
|
15
15
|
import { getPackageVersion, PACKAGE_NAME, pinnedPackageSpec } from "./version.js";
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
/** Path to the bundled package root (one level up from dist/). */
|
|
@@ -766,7 +766,7 @@ export function runInstallSkills(home = homedir(), options) {
|
|
|
766
766
|
"Pharaoh: no supported skill platform detected.",
|
|
767
767
|
"",
|
|
768
768
|
"Supported platforms:",
|
|
769
|
-
|
|
769
|
+
` • Claude Code — install the CLI from ${CLAUDE_CLI_SETUP_URL}`,
|
|
770
770
|
" • Codex — install from https://developers.openai.com/codex",
|
|
771
771
|
" • OpenClaw — install from https://openclaw.dev/install",
|
|
772
772
|
"",
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
/** Runs one `claude` CLI command; true on success. */
|
|
2
2
|
export type ClaudeCli = (args: string[]) => boolean;
|
|
3
|
+
/** Anthropic's Claude Code CLI install page: every platform and install method. */
|
|
4
|
+
export declare const CLAUDE_CLI_SETUP_URL = "https://code.claude.com/docs/en/setup";
|
|
5
|
+
/**
|
|
6
|
+
* Whether a `claude` CLI on PATH starts. It is launched the way `runClaude`
|
|
7
|
+
* launches it, so the check and the commands agree, with no `which` (minimal
|
|
8
|
+
* containers lack it). The Claude desktop app's bundled copy isn't on PATH.
|
|
9
|
+
*
|
|
10
|
+
* @returns True when `claude --version` runs.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hasClaude(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* What setup prints when no `claude` CLI is on PATH: npx registers everything
|
|
15
|
+
* through that CLI, the desktop app's copy doesn't count, and the CLI shares
|
|
16
|
+
* ~/.claude with the desktop app, so installing it covers the Code tab too.
|
|
17
|
+
*
|
|
18
|
+
* @param platform - Picks the one-line installer (PowerShell on Windows).
|
|
19
|
+
* @returns The lines to print.
|
|
20
|
+
*/
|
|
21
|
+
export declare function claudeCliMissing(platform?: NodeJS.Platform): string[];
|
|
3
22
|
/**
|
|
4
23
|
* A `claude` CLI runner for the Claude Code config under `home`: true when the
|
|
5
24
|
* command succeeded, false when it failed or `claude` isn't installed. Another
|
package/dist/setup.js
CHANGED
|
@@ -11,20 +11,54 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { NPX_COMMAND, printLines } from "./helpers.js";
|
|
13
13
|
import { pinnedPackageSpec } from "./version.js";
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/** Anthropic's Claude Code CLI install page: every platform and install method. */
|
|
15
|
+
export const CLAUDE_CLI_SETUP_URL = "https://code.claude.com/docs/en/setup";
|
|
16
|
+
/** Pharaoh's setup guide for agents other than Claude Code (stdio server, sign-in on first call). */
|
|
17
|
+
const HEADLESS_SETUP_URL = "https://pharaoh.so/docs/connect/headless";
|
|
18
|
+
/**
|
|
19
|
+
* Whether a `claude` CLI on PATH starts. It is launched the way `runClaude`
|
|
20
|
+
* launches it, so the check and the commands agree, with no `which` (minimal
|
|
21
|
+
* containers lack it). The Claude desktop app's bundled copy isn't on PATH.
|
|
22
|
+
*
|
|
23
|
+
* @returns True when `claude --version` runs.
|
|
24
|
+
*/
|
|
25
|
+
export function hasClaude() {
|
|
16
26
|
try {
|
|
17
|
-
|
|
18
|
-
execFileSync(which, ["claude"], {
|
|
19
|
-
encoding: "utf-8",
|
|
20
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
21
|
-
});
|
|
27
|
+
execFileSync("claude", ["--version"], { stdio: "pipe", timeout: 10_000 });
|
|
22
28
|
return true;
|
|
23
29
|
}
|
|
24
30
|
catch {
|
|
31
|
+
// A `claude` that can't be started is one setup can't use either.
|
|
25
32
|
return false;
|
|
26
33
|
}
|
|
27
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* What setup prints when no `claude` CLI is on PATH: npx registers everything
|
|
37
|
+
* through that CLI, the desktop app's copy doesn't count, and the CLI shares
|
|
38
|
+
* ~/.claude with the desktop app, so installing it covers the Code tab too.
|
|
39
|
+
*
|
|
40
|
+
* @param platform - Picks the one-line installer (PowerShell on Windows).
|
|
41
|
+
* @returns The lines to print.
|
|
42
|
+
*/
|
|
43
|
+
export function claudeCliMissing(platform = process.platform) {
|
|
44
|
+
// The shell goes in the label: anything after the command gets pasted with it.
|
|
45
|
+
const [shell, installer] = platform === "win32"
|
|
46
|
+
? [" (PowerShell)", "irm https://claude.ai/install.ps1 | iex"]
|
|
47
|
+
: ["", "curl -fsSL https://claude.ai/install.sh | bash"];
|
|
48
|
+
return [
|
|
49
|
+
"Pharaoh: the claude command isn't on your PATH, so setup stopped.",
|
|
50
|
+
"",
|
|
51
|
+
"npx adds Pharaoh to Claude Code through the Claude Code CLI. The desktop app runs its own copy of Claude Code, and that copy isn't on your PATH.",
|
|
52
|
+
"",
|
|
53
|
+
` Install the CLI${shell}: ${installer}`,
|
|
54
|
+
` Other ways: ${CLAUDE_CLI_SETUP_URL}`,
|
|
55
|
+
` Then, in a new terminal: ${NPX_COMMAND}`,
|
|
56
|
+
"",
|
|
57
|
+
"The CLI and the desktop app share ~/.claude, so the desktop app's Code tab gets Pharaoh too.",
|
|
58
|
+
`Not using Claude Code? OpenClaw: ${NPX_COMMAND} --install-skills. Other agents: ${HEADLESS_SETUP_URL}`,
|
|
59
|
+
"",
|
|
60
|
+
];
|
|
61
|
+
}
|
|
28
62
|
/** Run a claude CLI command, returning true on success. Surfaces stderr on failure. */
|
|
29
63
|
function runClaude(args, env = process.env) {
|
|
30
64
|
try {
|
|
@@ -128,7 +162,7 @@ export function runSetup(options) {
|
|
|
128
162
|
const silent = options?.silent ?? false;
|
|
129
163
|
const home = options?.home ?? homedir();
|
|
130
164
|
if (!options?.claude && !hasClaude()) {
|
|
131
|
-
printLines(
|
|
165
|
+
printLines(...claudeCliMissing());
|
|
132
166
|
return false;
|
|
133
167
|
}
|
|
134
168
|
const claude = options?.claude ?? claudeCliFor(home);
|
|
@@ -142,9 +176,9 @@ export function runSetup(options) {
|
|
|
142
176
|
claude(["mcp", "remove", "pharaoh", "-s", "project"]);
|
|
143
177
|
// Step 3: Register as global stdio MCP server, pinned to the running version
|
|
144
178
|
const spec = pinnedPackageSpec();
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
147
|
-
printLines("Pharaoh: failed to register MCP server.", `Try manually: ${
|
|
179
|
+
const addArgs = ["mcp", "add", "--scope", "user", "pharaoh", "--", "npx", spec];
|
|
180
|
+
if (!claude(addArgs)) {
|
|
181
|
+
printLines("Pharaoh: failed to register MCP server.", `Try manually: claude ${addArgs.join(" ")}`, "");
|
|
148
182
|
return false;
|
|
149
183
|
}
|
|
150
184
|
if (!silent)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pharaoh-so/mcp",
|
|
3
3
|
"mcpName": "so.pharaoh/pharaoh",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.53",
|
|
5
5
|
"description": "MCP proxy for Pharaoh — maps codebases into queryable knowledge graphs for AI agents. Enables Claude Code in headless environments (VPS, SSH, CI) via device flow auth.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
package/trace-app.html
CHANGED
|
@@ -6,60 +6,117 @@
|
|
|
6
6
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'none'; img-src 'none'; font-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'">
|
|
7
7
|
<title>Pharaoh Trace</title>
|
|
8
8
|
<style>
|
|
9
|
-
:root { color-scheme:dark; --
|
|
10
|
-
|
|
9
|
+
:root { color-scheme:dark; --stone:#2a2520; --rule:#3a352e; --track:#342e28; --hover:#332d27; --gold:#d4a843; --papyrus:#e8dcc8; --text:#c4b99a; --muted:#9d927b; --red:#f87171; --amber:#f0c855; --green:#4ade80;
|
|
10
|
+
--s0:#d4a843; --s1:#b08a3a; --s2:#8c6e31; --s3:#6e5728; --s4:#574522; --s-muted:#5f574a; --s-warning:#f0c855; --s-error:#ef4444;
|
|
11
|
+
--sans:"DM Sans",system-ui,-apple-system,"Segoe UI",sans-serif; --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace; }
|
|
12
|
+
:root[data-theme="light"] { color-scheme:light; --stone:#f7f3ea; --rule:#e2d8c4; --track:#ece4d3; --hover:#efe8da; --gold:#946c0f; --papyrus:#2a2520; --text:#4a4235; --muted:#71675a; --red:#b42318; --amber:#9a5b00; --green:#15803d;
|
|
13
|
+
--s0:#b08515; --s1:#c9a24a; --s2:#d9bb72; --s3:#e6cf98; --s4:#efdfb8; --s-muted:#cfc4ad; --s-warning:#e0a526; --s-error:#d93f2f; }
|
|
11
14
|
* { box-sizing:border-box; }
|
|
12
|
-
body { margin:0; min-width:0; }
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
15
|
+
html,body { margin:0; background:transparent; min-width:0; }
|
|
16
|
+
.label,.detail,.badge { overflow-wrap:anywhere; min-width:0; unicode-bidi:plaintext; }
|
|
17
|
+
.mark-s { font:600 12px var(--mono); width:1ch; }
|
|
18
|
+
#trace { background:var(--stone); color:var(--text); border:1px solid var(--rule); border-radius:6px; padding:9px 12px; font:13px/1.45 var(--sans); min-width:0; }
|
|
19
|
+
.head { display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 10px; }
|
|
20
|
+
.mark { color:var(--gold); font-size:14px; line-height:1; }
|
|
21
|
+
.headline { flex:1 1 16em; margin:0; min-width:0; overflow-wrap:anywhere; }
|
|
22
|
+
#kind { color:var(--muted); }
|
|
23
|
+
#target { color:var(--papyrus); font:600 13px/1.45 var(--mono); }
|
|
24
|
+
#target,.legend > span,.rows .label { unicode-bidi:isolate; }
|
|
25
|
+
#title { color:var(--papyrus); font-weight:600; }
|
|
26
|
+
#summary { margin-left:8px; }
|
|
27
|
+
.num { color:var(--gold); font:600 12.5px var(--mono); }
|
|
28
|
+
.meta { margin-left:auto; display:flex; gap:10px; white-space:nowrap; font-size:12px; }
|
|
29
|
+
#verdict[data-state="error"] { color:var(--red); } #verdict[data-state="warning"] { color:var(--amber); } #verdict[data-state="ok"] { color:var(--green); }
|
|
30
|
+
#sha { color:var(--muted); font:11.5px var(--mono); }
|
|
31
|
+
.bar { position:relative; display:flex; gap:2px; height:6px; margin:8px 0 0; border-radius:3px; overflow:hidden; background:var(--track); }
|
|
32
|
+
.bar i,.mini i { display:block; flex:0 1 0; min-width:0; }
|
|
33
|
+
.bar i[data-shown] { min-width:3px; }
|
|
34
|
+
.s0 { background:var(--s0); } .s1 { background:var(--s1); } .s2 { background:var(--s2); } .s3 { background:var(--s3); } .s4 { background:var(--s4); }
|
|
35
|
+
.muted { background:var(--s-muted); } .warning { background:var(--s-warning); } .error { background:var(--s-error); }
|
|
36
|
+
.foot { display:flex; align-items:baseline; gap:4px 14px; margin-top:6px; flex-wrap:wrap; }
|
|
37
|
+
.legend { display:flex; flex-wrap:wrap; gap:2px 14px; margin:0; color:var(--muted); font-size:12px; min-width:0; flex:1 1 auto; }
|
|
38
|
+
#trace[data-reveal] .legend { opacity:0; }
|
|
39
|
+
.legend b { color:var(--text); font:500 11.5px var(--mono); margin-left:4px; }
|
|
40
|
+
.sw { display:inline-block; width:8px; height:8px; border-radius:1px; margin-right:5px; }
|
|
41
|
+
.toggle { margin-left:auto; display:flex; align-items:baseline; gap:4px; border:0; background:none; padding:2px 0; color:var(--gold); font:12px var(--sans); cursor:pointer; white-space:nowrap; }
|
|
42
|
+
.toggle:hover { color:var(--papyrus); }
|
|
43
|
+
.toggle:focus-visible,summary:focus-visible { outline:2px solid var(--gold); outline-offset:2px; border-radius:2px; }
|
|
44
|
+
.chev { display:inline-block; font-size:10px; }
|
|
45
|
+
.toggle[aria-expanded="true"] .chev { transform:rotate(90deg); }
|
|
46
|
+
.caveat { margin:6px 0 0; color:var(--amber); font-size:12px; overflow-wrap:anywhere; }
|
|
47
|
+
.caveat span + span::before { content:" · "; color:var(--muted); }
|
|
48
|
+
.rows { list-style:none; margin:4px 0 0; padding:0; }
|
|
49
|
+
.rows li { display:flex; flex-wrap:wrap; align-items:baseline; gap:0 8px; padding:4px 0 5px; border-top:1px solid var(--rule); min-width:0; }
|
|
50
|
+
.rows li:first-child { border-top:0; }
|
|
51
|
+
.rows .kind { color:var(--muted); font-size:12.5px; }
|
|
52
|
+
.rows .label { color:var(--papyrus); font:12.5px var(--mono); }
|
|
53
|
+
.rows .prose { color:var(--text); font:12.5px var(--sans); }
|
|
54
|
+
.rows .detail { color:var(--text); font-size:12.5px; }
|
|
55
|
+
.rows .end { margin-left:auto; font-size:12px; white-space:nowrap; }
|
|
56
|
+
.rows .more { color:var(--muted); font-size:12px; }
|
|
57
|
+
.rows .mini { display:flex; gap:1px; flex:1 0 100%; height:3px; margin-top:3px; border-radius:2px; overflow:hidden; background:var(--track); }
|
|
58
|
+
[data-state="ok"] > .mark-s { color:var(--green); } [data-state="warning"] > .mark-s, [data-state="pending"] > .mark-s { color:var(--amber); } [data-state="error"] > .mark-s, .end[data-state="error"] { color:var(--red); } .end[data-state="warning"] { color:var(--amber); }
|
|
59
|
+
.drawer { display:grid; grid-template-rows:0fr; }
|
|
60
|
+
.drawer[data-open] { grid-template-rows:1fr; }
|
|
61
|
+
.drawer-inner { overflow:hidden; min-height:0; }
|
|
62
|
+
.drawer[data-open] .drawer-inner { border-top:1px solid var(--rule); margin-top:8px; padding-top:4px; }
|
|
63
|
+
ul.tree,.tree ul { list-style:none; margin:0; padding:0; }
|
|
64
|
+
.tree ul { margin:0 0 4px 6px; padding-left:10px; border-left:1px solid var(--rule); }
|
|
65
|
+
.tree summary,.leaf { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:10px; align-items:baseline; padding:3px 0; font-size:12.5px; min-width:0; }
|
|
66
|
+
.tree summary { list-style:none; cursor:pointer; color:var(--text); }
|
|
67
|
+
.tree summary::-webkit-details-marker { display:none; }
|
|
68
|
+
.tree summary:hover { background:var(--hover); }
|
|
69
|
+
.tree summary .label::before { content:"▸"; display:inline-block; color:var(--gold); font-size:10px; width:12px; }
|
|
70
|
+
.tree details[open] > summary .label::before { transform:rotate(90deg); }
|
|
71
|
+
.tree details[open] > summary .label { color:var(--gold); }
|
|
72
|
+
.leaf .label { color:var(--papyrus); font:12px var(--mono); }
|
|
73
|
+
.tree > li > .leaf { padding-left:12px; }
|
|
74
|
+
.tree > li > .leaf .label { color:var(--text); font:12.5px var(--sans); }
|
|
75
|
+
.tree .label .detail { color:var(--muted); font:11px var(--mono); margin-left:8px; }
|
|
76
|
+
.tree .label .caveat { color:var(--amber); font:11.5px var(--sans); margin:0 0 0 8px; }
|
|
77
|
+
.badge { color:var(--muted); font:11.5px var(--mono); text-align:right; white-space:nowrap; }
|
|
78
|
+
.badge[data-state="ok"] { color:var(--green); } .badge[data-state="warning"],.badge[data-state="pending"] { color:var(--amber); } .badge[data-state="error"] { color:var(--red); }
|
|
79
|
+
.meter { display:block; height:3px; margin-top:3px; border-radius:2px; background:var(--track); max-width:240px; }
|
|
80
|
+
.meter i { display:block; height:100%; border-radius:2px; background:var(--gold); }
|
|
81
|
+
.notes,.provenance { color:var(--muted); font-size:11.5px; margin:6px 0 2px; }
|
|
82
|
+
.notes p { margin:2px 0; overflow-wrap:anywhere; white-space:pre-wrap; }
|
|
83
|
+
#fallback { font:11.5px/1.5 var(--mono); color:var(--text); margin:6px 0 0; white-space:pre-wrap; overflow-wrap:anywhere; unicode-bidi:plaintext; }
|
|
84
|
+
@media(max-width:400px) { #trace { padding:8px 10px; } #summary { margin-left:0; } #summary:not([hidden]) { display:block; } .tree summary,.leaf { grid-template-columns:minmax(0,1fr); gap:0; } .badge { text-align:left; } }
|
|
85
|
+
/* Motion only for people who have not asked to reduce it. */
|
|
86
|
+
@media(prefers-reduced-motion:no-preference) {
|
|
87
|
+
.bar i,.mini i { transition:flex-grow .7s cubic-bezier(.22,.8,.24,1); }
|
|
88
|
+
.legend { transition:opacity .4s ease .2s; }
|
|
89
|
+
.chev { transition:transform .3s ease; }
|
|
90
|
+
.drawer { transition:grid-template-rows .3s ease; }
|
|
91
|
+
.tree summary .label::before { transition:transform .2s ease; }
|
|
92
|
+
#trace[data-state="pending"] .bar::after,#trace[data-state="waiting"] .bar::after { content:""; position:absolute; top:0; bottom:0; left:0; width:35%; background:linear-gradient(90deg,transparent,var(--gold),transparent); opacity:.55; animation:sweep 1.3s ease-in-out infinite; }
|
|
93
|
+
@keyframes sweep { from { transform:translateX(-100%); } to { transform:translateX(290%); } }
|
|
94
|
+
}
|
|
48
95
|
[hidden] { display:none; }
|
|
49
|
-
@media(max-width:400px) { #trace { padding:8px 10px 7px; } summary,.row { grid-template-columns:15px minmax(0,1fr); gap:4px; } .badge { grid-column:2; text-align:left; } .children { margin-left:9px; padding-left:7px; } }
|
|
50
|
-
@media(prefers-reduced-motion:reduce) { #trace[data-pending="true"] #status { animation:none; } }
|
|
51
96
|
</style>
|
|
52
97
|
</head>
|
|
53
98
|
<body>
|
|
54
|
-
<main id="trace" data-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
99
|
+
<main id="trace" data-state="waiting">
|
|
100
|
+
<div class="head">
|
|
101
|
+
<span class="mark" aria-hidden="true">☥</span>
|
|
102
|
+
<p class="headline"><span id="kind"></span><span id="target"></span><span id="title">Pharaoh</span><span id="summary" role="status" aria-live="polite">Waiting for the tool</span></p>
|
|
103
|
+
<span id="meta" class="meta"><span id="verdict" hidden></span><span id="sha" hidden></span></span>
|
|
104
|
+
</div>
|
|
105
|
+
<div id="bar" class="bar" aria-hidden="true"></div>
|
|
106
|
+
<div id="foot" class="foot">
|
|
107
|
+
<p id="legend" class="legend"></p>
|
|
108
|
+
<button id="toggle" class="toggle" type="button" aria-expanded="false" aria-controls="drawer" hidden><span id="toggle-label">Details</span><span class="chev" aria-hidden="true">▸</span></button>
|
|
109
|
+
</div>
|
|
110
|
+
<p id="caveat" class="caveat" hidden></p>
|
|
111
|
+
<ul id="rows" class="rows" hidden></ul>
|
|
112
|
+
<div id="drawer" class="drawer" inert>
|
|
113
|
+
<div class="drawer-inner">
|
|
114
|
+
<ul id="tree" class="tree" aria-label="Result evidence"></ul>
|
|
115
|
+
<pre id="fallback" hidden></pre>
|
|
116
|
+
<div id="notes" class="notes" hidden></div>
|
|
117
|
+
<p id="provenance" class="provenance" hidden></p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
63
120
|
</main>
|
|
64
121
|
<script>
|
|
65
122
|
(() => {
|
|
@@ -67,11 +124,18 @@
|
|
|
67
124
|
const META_KEY = "so.pharaoh/trace";
|
|
68
125
|
const PROTOCOL_VERSION = "2026-01-26";
|
|
69
126
|
// Kept in parity with TRACE_LIMITS in trace-contract.ts by the resource test.
|
|
70
|
-
const LIMITS = { bytes:65536, nodes:120, depth:6, label:512, detail:2048, badge:128, summary:1024, notes:16, note:1024, repo:256 };
|
|
127
|
+
const LIMITS = { bytes:65536, nodes:120, depth:6, label:512, detail:2048, badge:128, summary:1024, notes:16, note:1024, repo:256, segments:8 };
|
|
71
128
|
const MAX_TEXT = 32768;
|
|
129
|
+
const MAX_ROWS = 5;
|
|
130
|
+
const TONES = ["warning","error","muted"];
|
|
131
|
+
const STATES = ["ok","pending","warning","error"];
|
|
132
|
+
// What each tool is doing while it runs, shown with the argument it targets.
|
|
133
|
+
const VERBS = { get_blast_radius:"Tracing callers of", get_unused_code:"Checking reachability in", search_functions:"Searching for", get_codebase_map:"Mapping", get_module_context:"Reading module", pharaoh_recon:"Running recon on", pharaoh_account:"Checking account" };
|
|
72
134
|
const root = document.getElementById("trace");
|
|
73
|
-
const fields = Object.fromEntries(["
|
|
135
|
+
const fields = Object.fromEntries(["kind","target","title","summary","meta","verdict","sha","bar","caveat","rows","foot","legend","toggle","toggle-label","drawer","tree","fallback","notes","provenance"].map(id => [id,document.getElementById(id)]));
|
|
136
|
+
const compact = new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1});
|
|
74
137
|
const initId = "pharaoh-trace-initialize";
|
|
138
|
+
let tool = "";
|
|
75
139
|
let ready = false;
|
|
76
140
|
let closed = false;
|
|
77
141
|
let cancelled = false;
|
|
@@ -81,37 +145,76 @@
|
|
|
81
145
|
const record = value => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
82
146
|
const string = (value,max) => typeof value === "string" && value.length <= max;
|
|
83
147
|
const send = message => { if (!closed) window.parent.postMessage({jsonrpc:"2.0",...message},"*"); };
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
148
|
+
function element(tag,className,text) {
|
|
149
|
+
const node = document.createElement(tag);
|
|
150
|
+
if (className) node.className = className;
|
|
151
|
+
if (text !== undefined) node.textContent = text;
|
|
152
|
+
return node;
|
|
153
|
+
}
|
|
154
|
+
// Counts read at a glance: digit runs become gold monospace spans, everything else stays text.
|
|
155
|
+
function rich(target,text) {
|
|
156
|
+
target.replaceChildren();
|
|
157
|
+
for (const part of String(text || "").split(/(\b\d[\d,.]*\b)/)) {
|
|
158
|
+
if (part) target.append(/^\d/.test(part) ? element("span","num",part) : part);
|
|
159
|
+
}
|
|
88
160
|
}
|
|
89
161
|
function display(field,text) {
|
|
90
162
|
fields[field].textContent = text || "";
|
|
91
163
|
fields[field].hidden = !text;
|
|
92
164
|
}
|
|
165
|
+
// "Impact: attachTraceView" reads as a muted kind and a monospace target.
|
|
166
|
+
const splitTitle = title => /^([^:]{1,40}): (.+)$/.exec(title);
|
|
167
|
+
function headline(title,summary) {
|
|
168
|
+
const split = splitTitle(title);
|
|
169
|
+
display("kind",split ? `${split[1]} ` : "");
|
|
170
|
+
display("target",split ? split[2] : "");
|
|
171
|
+
display("title",split ? "" : title);
|
|
172
|
+
rich(fields.summary,summary);
|
|
173
|
+
fields.summary.hidden = !summary;
|
|
174
|
+
}
|
|
175
|
+
// The toggle ends the legend line, or the headline when there is no legend.
|
|
176
|
+
function place(withLegend) {
|
|
177
|
+
(withLegend ? fields.foot : fields.meta).append(fields.toggle);
|
|
178
|
+
fields.foot.hidden = !withLegend;
|
|
179
|
+
}
|
|
180
|
+
function setOpen(open) {
|
|
181
|
+
fields.drawer.toggleAttribute("data-open",open);
|
|
182
|
+
fields.drawer.toggleAttribute("inert",!open);
|
|
183
|
+
fields.toggle.setAttribute("aria-expanded",String(open));
|
|
184
|
+
fields["toggle-label"].textContent = open ? "Hide" : "Details";
|
|
185
|
+
}
|
|
186
|
+
function clearResult() {
|
|
187
|
+
setOpen(false);
|
|
188
|
+
for (const field of ["bar","rows","legend","tree","notes"]) fields[field].replaceChildren();
|
|
189
|
+
for (const field of ["verdict","sha","caveat","provenance","fallback"]) display(field,"");
|
|
190
|
+
delete fields.verdict.dataset.state;
|
|
191
|
+
fields.sha.title = "";
|
|
192
|
+
fields.bar.hidden = true;
|
|
193
|
+
fields.rows.hidden = true;
|
|
194
|
+
fields.notes.hidden = true;
|
|
195
|
+
fields.toggle.hidden = true;
|
|
196
|
+
}
|
|
93
197
|
function notes(items) {
|
|
94
|
-
fields.notes.
|
|
95
|
-
for (const text of items) {
|
|
96
|
-
const paragraph = document.createElement("p");
|
|
97
|
-
paragraph.textContent = text;
|
|
98
|
-
fields.notes.append(paragraph);
|
|
99
|
-
}
|
|
198
|
+
for (const text of items) fields.notes.append(element("p","",text));
|
|
100
199
|
fields.notes.hidden = items.length === 0;
|
|
101
200
|
}
|
|
102
|
-
function
|
|
103
|
-
fields.
|
|
104
|
-
fields.
|
|
105
|
-
display("summary","");
|
|
106
|
-
display("provenance","");
|
|
107
|
-
notes([]);
|
|
201
|
+
function caveats(items) {
|
|
202
|
+
fields.caveat.replaceChildren(...items.map(text => element("span","",text)));
|
|
203
|
+
fields.caveat.hidden = items.length === 0;
|
|
108
204
|
}
|
|
109
205
|
function fallback(title,text,notice,state="neutral") {
|
|
110
206
|
clearResult();
|
|
111
|
-
|
|
207
|
+
root.dataset.state = state === "error" ? "error" : "ready";
|
|
208
|
+
const first = text.split("\n").find(line => line.trim()) || "";
|
|
209
|
+
headline(title,first.length > 160 ? `${first.slice(0,159)}…` : first);
|
|
112
210
|
display("fallback",text);
|
|
113
|
-
|
|
211
|
+
if (state === "error" || state === "cancelled") {
|
|
212
|
+
fields.verdict.dataset.state = state === "error" ? "error" : "warning";
|
|
213
|
+
display("verdict",state === "error" ? "Failed" : "Cancelled");
|
|
214
|
+
}
|
|
114
215
|
if (notice) notes([notice]);
|
|
216
|
+
fields.toggle.hidden = !text;
|
|
217
|
+
place(false);
|
|
115
218
|
}
|
|
116
219
|
function readableText(result) {
|
|
117
220
|
if (!record(result) || !Array.isArray(result.content)) return {text:"No readable text was returned. Read the tool response in chat.",omitted:false};
|
|
@@ -129,12 +232,20 @@
|
|
|
129
232
|
}
|
|
130
233
|
return {text:text || "No readable text was returned. Read the tool response in chat.",omitted};
|
|
131
234
|
}
|
|
235
|
+
function validGlance(glance) {
|
|
236
|
+
if (glance === undefined) return true;
|
|
237
|
+
if (!record(glance)) return false;
|
|
238
|
+
if (glance.segments !== undefined && (!Array.isArray(glance.segments) || glance.segments.length > LIMITS.segments || !glance.segments.every(s => record(s) && string(s.label,LIMITS.badge) && Number.isFinite(s.value) && s.value >= 0 && (s.tone === undefined || TONES.includes(s.tone))))) return false;
|
|
239
|
+
if (glance.verdict !== undefined && (!record(glance.verdict) || !string(glance.verdict.label,LIMITS.badge) || (glance.verdict.status !== undefined && !["ok","warning","error"].includes(glance.verdict.status)))) return false;
|
|
240
|
+
return glance.caveat === undefined || string(glance.caveat,LIMITS.note);
|
|
241
|
+
}
|
|
132
242
|
function validView(view) {
|
|
133
243
|
try {
|
|
134
244
|
if (!record(view) || view.version !== 1 || !string(view.title,LIMITS.label) || !view.title.trim() || !Array.isArray(view.nodes)) return false;
|
|
135
245
|
if (new TextEncoder().encode(JSON.stringify(view)).byteLength > LIMITS.bytes) return false;
|
|
136
246
|
if (view.repo !== undefined && !string(view.repo,LIMITS.repo)) return false;
|
|
137
247
|
if (view.summary !== undefined && !string(view.summary,LIMITS.summary)) return false;
|
|
248
|
+
if (!validGlance(view.glance)) return false;
|
|
138
249
|
if (view.notes !== undefined && (!Array.isArray(view.notes) || view.notes.length > LIMITS.notes || !view.notes.every(note => string(note,LIMITS.note)))) return false;
|
|
139
250
|
if (view.freshness !== undefined) {
|
|
140
251
|
const value = view.freshness;
|
|
@@ -145,71 +256,145 @@
|
|
|
145
256
|
if (++count > LIMITS.nodes || depth > LIMITS.depth || !record(node) || !string(node.label,LIMITS.label) || !node.label.trim()) return false;
|
|
146
257
|
if (node.detail !== undefined && !string(node.detail,LIMITS.detail)) return false;
|
|
147
258
|
if (node.badge !== undefined && !string(node.badge,LIMITS.badge)) return false;
|
|
148
|
-
if (node.status !== undefined && !
|
|
259
|
+
if (node.status !== undefined && !STATES.includes(node.status)) return false;
|
|
149
260
|
if (node.meter !== undefined && (!record(node.meter) || !Number.isFinite(node.meter.value) || !Number.isFinite(node.meter.total) || node.meter.total <= 0 || node.meter.value < 0 || node.meter.value > node.meter.total)) return false;
|
|
261
|
+
if (!validGlance(node.glance)) return false;
|
|
150
262
|
return node.children === undefined || (Array.isArray(node.children) && node.children.every(child => visit(child,depth + 1)));
|
|
151
263
|
};
|
|
152
264
|
return view.nodes.every(node => visit(node,1));
|
|
153
265
|
} catch { return false; }
|
|
154
266
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
267
|
+
// Segments start empty and grow to their share once painted: the one motion on arrival.
|
|
268
|
+
function segments(target,items) {
|
|
269
|
+
const total = items.reduce((sum,item) => sum + item.value,0);
|
|
270
|
+
if (!total) return [];
|
|
271
|
+
let shade = 0;
|
|
272
|
+
return items.filter(item => item.value > 0).map((item,index) => {
|
|
273
|
+
const slice = element("i",item.tone || `s${Math.min(shade++,4)}`);
|
|
274
|
+
slice.style.transitionDelay = `${index * 70}ms`;
|
|
275
|
+
slice.dataset.share = String(item.value / total);
|
|
276
|
+
target.append(slice);
|
|
277
|
+
return slice;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function grow(slices) {
|
|
281
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
282
|
+
for (const slice of slices) { slice.style.flexGrow = slice.dataset.share; slice.dataset.shown = ""; }
|
|
283
|
+
delete root.dataset.reveal;
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
function legend(items) {
|
|
287
|
+
let shade = 0;
|
|
288
|
+
for (const item of items.filter(item => item.value > 0)) {
|
|
289
|
+
const entry = element("span");
|
|
290
|
+
entry.append(element("span",`sw ${item.tone || `s${Math.min(shade++,4)}`}`),item.label,element("b","",compact.format(item.value)));
|
|
291
|
+
fields.legend.append(entry);
|
|
292
|
+
}
|
|
160
293
|
}
|
|
161
|
-
function
|
|
294
|
+
function verdict(target,value) {
|
|
295
|
+
if (!value) return;
|
|
296
|
+
target.textContent = value.label;
|
|
297
|
+
if (value.status) target.dataset.state = value.status;
|
|
298
|
+
target.hidden = false;
|
|
299
|
+
}
|
|
300
|
+
// Sections (recon) and status lines (states, named checks) stay visible; groups open on demand.
|
|
301
|
+
function rowFor(node,slices) {
|
|
302
|
+
const item = element("li");
|
|
303
|
+
if (node.status) {
|
|
304
|
+
item.dataset.state = node.status;
|
|
305
|
+
item.append(element("span","mark-s",{ok:"✓",pending:"…",warning:"!",error:"×"}[node.status]));
|
|
306
|
+
}
|
|
307
|
+
const split = splitTitle(node.label);
|
|
308
|
+
if (split) item.append(element("span","kind",split[1]));
|
|
309
|
+
// Names read in monospace; sentences (state messages) read as prose.
|
|
310
|
+
item.append(element("span",split || !/\s/.test(node.label) ? "label" : "label prose",split ? split[2] : node.label));
|
|
311
|
+
if (node.detail) { const detail = element("span","detail"); rich(detail,node.detail); item.append(detail); }
|
|
312
|
+
const end = element("span","end",node.badge || "");
|
|
313
|
+
end.hidden = !node.badge;
|
|
314
|
+
if (node.glance?.verdict) { end.hidden = true; verdict(end,node.glance.verdict); }
|
|
315
|
+
item.append(end);
|
|
316
|
+
if (node.glance?.caveat) item.append(element("span","detail caveat",node.glance.caveat));
|
|
317
|
+
if (node.glance?.segments?.length) {
|
|
318
|
+
const mini = element("span","mini");
|
|
319
|
+
mini.setAttribute("aria-hidden","true");
|
|
320
|
+
slices.push(...segments(mini,node.glance.segments));
|
|
321
|
+
item.append(mini);
|
|
322
|
+
}
|
|
323
|
+
return item;
|
|
324
|
+
}
|
|
325
|
+
function renderNode(node) {
|
|
162
326
|
const item = element("li");
|
|
163
327
|
const group = Array.isArray(node.children) && node.children.length > 0;
|
|
164
|
-
const
|
|
165
|
-
if (group && open) container.open = true;
|
|
166
|
-
const row = element(group ? "summary" : "div",group ? "" : "row");
|
|
167
|
-
const branch = element("span","branch",group ? "" : last ? "└─" : "├─");
|
|
168
|
-
branch.setAttribute("aria-hidden","true");
|
|
328
|
+
const row = element(group ? "summary" : "div",group ? "" : "leaf");
|
|
169
329
|
const label = element("span","label",node.label);
|
|
170
330
|
if (node.detail) label.append(element("span","detail",node.detail));
|
|
171
|
-
const symbol = {ok:"[ok]",pending:"[...]",warning:"[!]",error:"[!]"}[node.status] || "";
|
|
172
|
-
const badge = element("span","badge",[symbol,node.badge].filter(Boolean).join(" "));
|
|
173
|
-
if (node.status) badge.dataset.state = node.status;
|
|
174
|
-
row.append(branch,label,badge);
|
|
175
|
-
container.append(row);
|
|
176
331
|
if (node.meter) {
|
|
177
|
-
const filled = Math.round(node.meter.value / node.meter.total * 12);
|
|
178
332
|
const meter = element("span","meter");
|
|
179
|
-
meter.
|
|
333
|
+
meter.title = `${node.meter.value} / ${node.meter.total}`;
|
|
334
|
+
const fill = element("i");
|
|
335
|
+
fill.style.width = `${Math.max(1,Math.round(node.meter.value / node.meter.total * 100))}%`;
|
|
336
|
+
meter.append(fill);
|
|
180
337
|
label.append(meter);
|
|
181
338
|
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
339
|
+
if (node.glance?.caveat) label.append(element("span","caveat",node.glance.caveat));
|
|
340
|
+
const symbol = {ok:"✓",pending:"…",warning:"!",error:"×"}[node.status] || "";
|
|
341
|
+
const badge = element("span","badge",[symbol,node.badge || node.glance?.verdict?.label].filter(Boolean).join(" "));
|
|
342
|
+
const state = node.status || node.glance?.verdict?.status;
|
|
343
|
+
if (state) badge.dataset.state = state;
|
|
344
|
+
row.append(label,badge);
|
|
345
|
+
if (!group) { item.append(row); return item; }
|
|
346
|
+
const details = element("details");
|
|
347
|
+
const children = element("ul");
|
|
348
|
+
for (const child of node.children) children.append(renderNode(child));
|
|
349
|
+
details.append(row,children);
|
|
350
|
+
item.append(details);
|
|
188
351
|
return item;
|
|
189
352
|
}
|
|
190
353
|
function renderView(view) {
|
|
191
354
|
clearResult();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
355
|
+
cancelled = false;
|
|
356
|
+
root.dataset.state = "ready";
|
|
357
|
+
root.dataset.reveal = "";
|
|
358
|
+
const glance = view.glance || {};
|
|
359
|
+
headline(view.title,view.summary);
|
|
360
|
+
verdict(fields.verdict,glance.verdict);
|
|
361
|
+
const freshness = view.freshness;
|
|
362
|
+
if (freshness?.mappedCommit) {
|
|
363
|
+
display("sha",`@${/^[0-9a-f]{7,}$/i.test(freshness.mappedCommit) ? freshness.mappedCommit.slice(0,7) : freshness.mappedCommit.slice(0,12)}`);
|
|
364
|
+
fields.sha.title = `Mapped ${freshness.mappedCommit}${freshness.lastAnalyzed ? ` as of ${freshness.lastAnalyzed}` : ""}`;
|
|
199
365
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
366
|
+
if (freshness) display("provenance",[freshness.mappedCommit ? `Mapped ${freshness.mappedCommit}` : "Mapped commit unknown",freshness.lastAnalyzed ? `as of ${freshness.lastAnalyzed}` : "map time unknown",`status: ${freshness.status}`].join(" · "));
|
|
367
|
+
caveats([glance.caveat,freshness?.status === "stale" ? "Map is behind the latest push" : ""].filter(Boolean));
|
|
368
|
+
const slices = segments(fields.bar,glance.segments || []);
|
|
369
|
+
fields.bar.hidden = slices.length === 0;
|
|
370
|
+
legend(glance.segments || []);
|
|
371
|
+
// Up to MAX_ROWS rows, warnings and verdicts first, shown in their original order.
|
|
372
|
+
const candidates = view.nodes.filter(node => node.glance || node.status);
|
|
373
|
+
const urgent = node => node.status === "error" || node.status === "warning" || node.glance?.verdict || node.glance?.caveat;
|
|
374
|
+
const chosen = new Set([...new Set([...candidates.filter(urgent),...candidates])].slice(0,MAX_ROWS));
|
|
375
|
+
const visible = candidates.filter(node => chosen.has(node));
|
|
376
|
+
for (const node of visible) fields.rows.append(rowFor(node,slices));
|
|
377
|
+
if (candidates.length > visible.length) fields.rows.append(element("li","more",`+${candidates.length - visible.length} more in Details`));
|
|
378
|
+
fields.rows.hidden = visible.length === 0;
|
|
379
|
+
for (const node of view.nodes) if (!visible.includes(node) || node.children?.length) fields.tree.append(renderNode(node));
|
|
380
|
+
// One click on Details shows evidence: the first group is already open inside the drawer.
|
|
381
|
+
fields.tree.querySelector("details")?.setAttribute("open","");
|
|
203
382
|
notes(view.notes || []);
|
|
204
|
-
|
|
383
|
+
fields.toggle.hidden = !fields.tree.childElementCount && fields.notes.hidden && fields.provenance.hidden;
|
|
384
|
+
place(fields.legend.childElementCount > 0);
|
|
385
|
+
grow(slices);
|
|
205
386
|
}
|
|
206
387
|
function input(params) {
|
|
207
388
|
cancelled = false;
|
|
208
389
|
clearResult();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
390
|
+
root.dataset.state = "pending";
|
|
391
|
+
const args = record(params.arguments) ? params.arguments : {};
|
|
392
|
+
const target = ["entity","query","module","repo"].map(key => args[key]).find(value => string(value,LIMITS.repo) && value.trim());
|
|
393
|
+
const verb = Object.hasOwn(VERBS,tool) ? VERBS[tool] : "Querying the Pharaoh graph";
|
|
394
|
+
headline("","");
|
|
395
|
+
display("kind",target ? `${verb} ` : `${verb}…`);
|
|
396
|
+
display("target",target ? `${target}…` : "");
|
|
397
|
+
fields.bar.hidden = false;
|
|
213
398
|
}
|
|
214
399
|
function result(params) {
|
|
215
400
|
if (cancelled) return;
|
|
@@ -226,7 +411,7 @@
|
|
|
226
411
|
function context(params) {
|
|
227
412
|
if (params.theme === "dark" || params.theme === "light") document.documentElement.dataset.theme = params.theme;
|
|
228
413
|
const name = params.toolInfo?.tool?.name;
|
|
229
|
-
if (string(name,128))
|
|
414
|
+
if (string(name,128)) tool = name;
|
|
230
415
|
}
|
|
231
416
|
function size() {
|
|
232
417
|
if (!ready || closed || resizeFrame) return;
|
|
@@ -244,7 +429,9 @@
|
|
|
244
429
|
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(size) : null;
|
|
245
430
|
observer?.observe(root);
|
|
246
431
|
root.addEventListener("toggle",size,true);
|
|
432
|
+
root.addEventListener("transitionend",size);
|
|
247
433
|
window.addEventListener("resize",size);
|
|
434
|
+
fields.toggle.addEventListener("click",() => { setOpen(fields.toggle.getAttribute("aria-expanded") !== "true"); size(); });
|
|
248
435
|
function receive(event) {
|
|
249
436
|
if (closed || event.source !== window.parent || !record(event.data) || event.data.jsonrpc !== "2.0") return;
|
|
250
437
|
const message = event.data;
|
|
@@ -271,7 +458,7 @@
|
|
|
271
458
|
clearTimeout(initTimeout);
|
|
272
459
|
cancelAnimationFrame(resizeFrame);
|
|
273
460
|
observer?.disconnect();
|
|
274
|
-
root.dataset.
|
|
461
|
+
root.dataset.state = "ready";
|
|
275
462
|
window.removeEventListener("message",receive);
|
|
276
463
|
} else send({id:message.id,error:{code:-32601,message:"Unsupported request"}});
|
|
277
464
|
return;
|
|
@@ -289,7 +476,7 @@
|
|
|
289
476
|
const initTimeout = setTimeout(() => {
|
|
290
477
|
if (!ready && !closed) fallback("Text response available","Read the tool response in chat.","Trace has not received a host connection.");
|
|
291
478
|
},10000);
|
|
292
|
-
send({id:initId,method:"ui/initialize",params:{appInfo:{name:"Pharaoh Trace",version:"1.
|
|
479
|
+
send({id:initId,method:"ui/initialize",params:{appInfo:{name:"Pharaoh Trace",version:"1.2.0"},appCapabilities:{availableDisplayModes:["inline"]},protocolVersion:PROTOCOL_VERSION}});
|
|
293
480
|
})();
|
|
294
481
|
</script>
|
|
295
482
|
</body>
|