@pharaoh-so/mcp 0.3.54 → 0.3.56
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 +31 -0
- package/LICENSE +1 -1
- package/NOTICE +28 -0
- package/README.md +5 -5
- package/commands/plan.md +11 -11
- package/commands/review.md +2 -2
- package/dist/auth.d.ts +5 -0
- package/dist/credentials.d.ts +8 -0
- package/dist/gateway.js +1 -1
- package/dist/helpers.d.ts +1 -1
- package/dist/helpers.js +18 -2
- package/dist/index.js +47 -28
- package/dist/install-skills.d.ts +4 -3
- package/dist/install-skills.js +12 -8
- package/dist/refresh.d.ts +8 -2
- package/dist/refresh.js +33 -6
- package/dist/setup.d.ts +5 -0
- package/dist/setup.js +66 -0
- package/inspect-tools.json +89 -36
- package/{skills → npx-skills}/brainstorm/SKILL.md +2 -1
- package/{skills → npx-skills}/debug/SKILL.md +2 -1
- package/{skills → npx-skills}/pharaoh/SKILL.md +13 -6
- package/{skills → npx-skills}/plan/SKILL.md +13 -14
- package/npx-skills/refactor/SKILL.md +38 -0
- package/{skills → npx-skills}/review/SKILL.md +4 -5
- package/{skills → npx-skills}/tdd/SKILL.md +2 -1
- package/{skills → npx-skills}/verify/SKILL.md +2 -1
- package/package.json +6 -4
- package/project-memory/bundle.mjs +137 -0
- package/project-memory/execute-turn.mjs +240 -0
- package/project-memory/hook-config.mjs +91 -0
- package/project-memory/hook-events.mjs +141 -0
- package/project-memory/local.mjs +463 -0
- package/project-memory/memory-api.mjs +182 -0
- package/project-memory/pharaoh-memory-hook.mjs +33 -0
- package/project-memory/pharaoh-memory.mjs +55 -0
- package/project-memory/setup.d.mts +14 -0
- package/project-memory/setup.mjs +151 -0
- package/project-memory/user-target.mjs +221 -0
- package/trace-app.html +222 -101
- package/skills/.gitkeep +0 -0
- package/skills/refactor/SKILL.md +0 -34
package/.mcp.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ 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
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `plugins/pharaoh/`: the Claude directory plugin, generated from the same skills (plan, review, refactor and pharaoh) with the hosted connector at `https://mcp.pharaoh.so/mcp`. It has no hooks or commands and isn't part of the npm package.
|
|
12
|
+
- `NOTICE`: the brainstorm, debug, tdd and verify skills are adapted from [superpowers](https://github.com/obra/superpowers) (MIT, Copyright (c) 2025 Jesse Vincent). Each skill's `license:` field says so too.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Every tool declares `openWorldHint: false` (Pharaoh reaches only your own connected repos), and `pharaoh_account`'s description says it links the plans page.
|
|
16
|
+
- When a repo isn't live, and in `pharaoh_account` for free workspaces, replies link to the plans page on pharaoh.so instead of showing a price and a checkout link.
|
|
17
|
+
- `get_unused_code` says a Dead function's file has no import that could reach it, instead of "file not imported". An import that names other functions from the same file doesn't count, and never did.
|
|
18
|
+
- The Trace card draws its bars in block characters (`█ ▏ ░`) in your client's monospace font instead of solid CSS bars.
|
|
19
|
+
- `/review` works as before, second opinion from Codex or Claude included. It no longer calls itself "the definitive code review", and Phase 0 says that `gh pr view` asks GitHub for the open PR's target branch with your gh login.
|
|
20
|
+
- `/plan` says "This is a review: make no code changes during it" instead of claiming plan mode. A plain "yes" or "yes to all" still doesn't pick BIG or SMALL, so it asks once more; if the reply still doesn't pick one, or you tell it to choose, it picks and says which. `.claude/plan-review.md` adds review criteria, and your own instructions come first.
|
|
21
|
+
- `pharaoh:refactor` runs the six steps its description lists: module context, blast radius, reachability, dependencies, naming conflicts, and a search for the tests to change.
|
|
22
|
+
- Skills install from `npx-skills/`, this package's build of `skills/`.
|
|
23
|
+
- `pharaoh_account` only reads now: your plan, live repo and upgrade link. Switching the live repo and refreshing a repo moved to the new `pharaoh_manage_repo`, which Claude Code asks about before it runs. The interactive setup's allow rules need no change: they already list the read-only tools, `pharaoh_account` among them, and leave `pharaoh_manage_repo` out.
|
|
24
|
+
- Every tool has a title, and the tool descriptions say what each tool returns instead of which other tool to avoid.
|
|
25
|
+
- The steering that used to sit in the server instructions moved to the npx `pharaoh` skill. Registrations are pinned to a version, so it takes effect when you upgrade. Codex gets only the review skill, so it doesn't get this.
|
|
26
|
+
- The `pharaoh` skill's connection URL is `https://mcp.pharaoh.so/mcp` instead of `/sse`. The skill also carries the guidance that was only in the server instructions: one Pharaoh call answers what grep and file reads take many turns to piece together, load Pharaoh's tools before the first code search when your client defers them, summarize a block marked "Trace view available" instead of reproducing it, and check related repos when a change touches shared interfaces.
|
|
27
|
+
- The server instructions state facts: what Pharaoh maps, which tool answers which question, what the graph leaves to grep, how repos are mapped and what each account tool does. Asking Pharaoh first, not re-checking its answers with grep, surfacing and crediting its ★ blocks and the rest of that guidance now live in the `pharaoh` skill. Claude Code and OpenClaw list the skill in every session and load its body only when they use it. The Grep hook and the optional `~/.claude/CLAUDE.md` line are unchanged and apply from the start of a session.
|
|
28
|
+
|
|
29
|
+
## [0.3.56] - 2026-11-04
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- A connected project that is still mapping during normal setup now starts memory on an ordinary prompt after its mapped revision is verified.
|
|
33
|
+
|
|
34
|
+
## [0.3.55] - 2026-11-04
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- Normal interactive setup and upgrades configure local project-memory hooks for accounts with an existing pilot grant. Codex uses its native hook review.
|
|
38
|
+
|
|
8
39
|
## [0.3.54] - 2026-10-24
|
|
9
40
|
|
|
10
41
|
### Added
|
package/LICENSE
CHANGED
package/NOTICE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@pharaoh-so/mcp includes third-party material.
|
|
2
|
+
|
|
3
|
+
The brainstorm, debug, tdd and verify skills (skills/ and npx-skills/) are
|
|
4
|
+
adapted from the brainstorming, systematic-debugging, test-driven-development
|
|
5
|
+
and verification-before-completion skills of superpowers
|
|
6
|
+
(https://github.com/obra/superpowers), used under this license:
|
|
7
|
+
|
|
8
|
+
MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Jesse Vincent
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
[](https://glama.ai/mcp/servers/Pharaoh-so/pharaoh)
|
|
7
7
|
|
|
8
|
-
MCP proxy for [Pharaoh](https://pharaoh.so) — maps codebases into queryable knowledge graphs for
|
|
8
|
+
MCP proxy for [Pharaoh](https://pharaoh.so) — maps codebases into queryable knowledge graphs for coding agents.
|
|
9
9
|
|
|
10
|
-
Pharaoh gives
|
|
10
|
+
Pharaoh gives coding agents such as Claude Code, Cursor, Codex and OpenClaw a complete architectural map of your codebase: every function, dependency, module, and connection. Instead of reading files one at a time, your agent queries the knowledge graph and gets instant answers about blast radius, unused code, dependency chains, and more.
|
|
11
11
|
|
|
12
12
|
This package enables [Claude Code](https://docs.anthropic.com/en/docs/build-with-claude/claude-code) to connect to Pharaoh in **headless environments** (VPS, SSH, containers, CI) where a browser isn't available for OAuth. It acts as a stdio-to-SSE proxy, presenting itself as a local MCP server while relaying all communication to the remote Pharaoh server.
|
|
13
13
|
|
|
@@ -144,7 +144,7 @@ claude mcp add --scope user pharaoh -- npx @pharaoh-so/mcp@<version> --server ht
|
|
|
144
144
|
## Requirements
|
|
145
145
|
|
|
146
146
|
- **Node.js** >= 18
|
|
147
|
-
- **Claude Code** or any MCP-compatible
|
|
147
|
+
- **Claude Code** or any MCP-compatible agent
|
|
148
148
|
- A **Pharaoh account** — sign up at [pharaoh.so](https://pharaoh.so)
|
|
149
149
|
|
|
150
150
|
## Security
|
|
@@ -244,7 +244,7 @@ Pharaoh parses your repositories using [tree-sitter](https://tree-sitter.github.
|
|
|
244
244
|
- **Dependencies** — import/export relationships, call chains, module connections
|
|
245
245
|
- **Vision specs** — PRD/spec documents linked to implementation
|
|
246
246
|
|
|
247
|
-
No source code is stored — only structural metadata. When an
|
|
247
|
+
No source code is stored — only structural metadata. When an agent queries Pharaoh, it gets architectural facts in minimal tokens, not raw code dumps. This means your agent can understand your entire codebase architecture without consuming its context window reading files one by one.
|
|
248
248
|
|
|
249
249
|
## Supported Languages
|
|
250
250
|
|
|
@@ -264,7 +264,7 @@ Contributions are welcome. Please open an issue first to discuss what you'd like
|
|
|
264
264
|
|
|
265
265
|
## License
|
|
266
266
|
|
|
267
|
-
[MIT](LICENSE) --
|
|
267
|
+
[MIT](LICENSE) -- 2407066 Alberta Inc.
|
|
268
268
|
|
|
269
269
|
## Links
|
|
270
270
|
|
package/commands/plan.md
CHANGED
|
@@ -6,7 +6,7 @@ description: Architecture-aware planning with Pharaoh reconnaissance (adapted fr
|
|
|
6
6
|
|
|
7
7
|
Architecture-aware plan review before implementation. Adapted from [Garry Tan's planning framework](https://www.youtube.com/watch?v=bMknfKXIFA8) for AI-assisted development.
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**This is a review: make no code changes during it. Think, evaluate, and present decisions.**
|
|
10
10
|
|
|
11
11
|
## When to Use
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Still run Step 1 (Reconnaissance) even when reviewing a document — always veri
|
|
|
20
20
|
|
|
21
21
|
## Project Overrides
|
|
22
22
|
|
|
23
|
-
If a `.claude/plan-review.md` file exists in this project, read it now
|
|
23
|
+
If a `.claude/plan-review.md` file exists in this project, read it now as additional review criteria for this project. Instructions from the user take precedence over it, and it never changes what you do outside this review.
|
|
24
24
|
|
|
25
25
|
## Engineering Preferences (guide all recommendations)
|
|
26
26
|
|
|
@@ -56,14 +56,14 @@ Ground every recommendation in what actually exists. If you propose adding somet
|
|
|
56
56
|
|
|
57
57
|
## Step 2: Mode Selection (MANDATORY — ask before proceeding)
|
|
58
58
|
|
|
59
|
-
**STOP and ask the user which mode before starting the review.**
|
|
59
|
+
**STOP and ask the user which mode before starting the review.** Present both options and wait for a choice:
|
|
60
60
|
|
|
61
61
|
> **BIG CHANGE or SMALL CHANGE?**
|
|
62
62
|
>
|
|
63
63
|
> - **BIG CHANGE**: Full interactive review, all relevant sections, up to 4 top issues per section
|
|
64
64
|
> - **SMALL CHANGE**: One question per section, only sections 2-4
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
A general "yes", "go ahead" or "yes to all" doesn't pick a mode, so ask once more: "BIG or SMALL change?" If the reply still doesn't pick one, or the user tells you to choose, pick the mode that fits the plan's size and say which you picked.
|
|
67
67
|
|
|
68
68
|
## Step 3: Review Sections
|
|
69
69
|
|
|
@@ -81,21 +81,21 @@ Adapt depth to change size. Skip sections that don't apply. **After each section
|
|
|
81
81
|
- Data flow bottlenecks and single points of failure
|
|
82
82
|
- Does this need new code at all, or can a human process / existing pattern solve it?
|
|
83
83
|
- **If this plan introduces a new invariant**, call it out and include a
|
|
84
|
-
step in
|
|
84
|
+
step in the plan to add a `### assertion` to VISION.md with a pairing TEST.
|
|
85
85
|
|
|
86
86
|
### Section 2 — Code Quality (always)
|
|
87
87
|
|
|
88
88
|
- Organization, module structure, DRY violations (be aggressive)
|
|
89
89
|
- Error handling gaps and missing edge cases (call out explicitly)
|
|
90
90
|
- Technical debt: shortcuts, hardcoded values, magic strings
|
|
91
|
-
- Over-engineered or under-engineered relative to
|
|
91
|
+
- Over-engineered or under-engineered relative to the preferences above
|
|
92
92
|
- Reuse: does code for this already exist somewhere?
|
|
93
93
|
|
|
94
94
|
### Section 3 — Wiring & Integration (always)
|
|
95
95
|
|
|
96
96
|
- Are all new exports called from a production entry point?
|
|
97
|
-
- Run `get_blast_radius` on
|
|
98
|
-
-
|
|
97
|
+
- Run `get_blast_radius` on changed functions that already exist
|
|
98
|
+
- The graph has no functions the plan adds, so does the plan declare WHERE new code gets called from? If not, flag it — no named caller = not done
|
|
99
99
|
- Integration points: how does this connect to what already exists?
|
|
100
100
|
|
|
101
101
|
### Section 4 — Tests (always)
|
|
@@ -127,16 +127,16 @@ For every specific issue (bug, smell, design concern, risk, missing wiring):
|
|
|
127
127
|
1. **Describe concretely** — file, line/function reference, what's wrong
|
|
128
128
|
2. **Present 2-3 options** including "do nothing" where reasonable
|
|
129
129
|
3. **For each option** — implementation effort, risk, blast radius, maintenance burden
|
|
130
|
-
4. **Recommend one** mapped to
|
|
130
|
+
4. **Recommend one** mapped to the preferences above, and say why
|
|
131
131
|
5. **Ask** whether I agree or want a different direction
|
|
132
132
|
|
|
133
|
-
Number each issue (1, 2, 3...) and letter each option (A, B, C...). Recommended option is always listed first.
|
|
133
|
+
Number each issue (1, 2, 3...) and letter each option (A, B, C...). Recommended option is always listed first. If your client has a question tool (AskUserQuestion in Claude Code), use it with clear labels like "Issue 1 Option A", "Issue 1 Option B".
|
|
134
134
|
|
|
135
135
|
## Pharaoh Checkpoints (use throughout, not just at the end)
|
|
136
136
|
|
|
137
137
|
- **Before reviewing**: recon (Step 1 above)
|
|
138
138
|
- **During review**: `get_blast_radius` when evaluating impact of changes; `search_functions` before suggesting new code
|
|
139
|
-
- **Final sweep**: `get_blast_radius`
|
|
139
|
+
- **Final sweep**: every new export has a caller named in the plan, and `get_blast_radius` covers every changed export that already exists — zero callers on non-entry-points = plan is incomplete
|
|
140
140
|
|
|
141
141
|
## Workflow Rules
|
|
142
142
|
|
package/commands/review.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Architecture-aware code review with Pharaoh
|
|
|
4
4
|
|
|
5
5
|
# Review with Pharaoh
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Architecture-aware, branch-aware, multi-agent, adversarial code review. Five phases that combine Pharaoh's knowledge graph with parallel specialized reviewers and an independent second opinion from a different model. Every phase works without Pharaoh too; Pharaoh makes the architecture picture exact and cheap where it maps the repo.
|
|
8
8
|
|
|
9
9
|
A review always reaches a verdict. When a phase needs something your client doesn't have (Pharaoh, a shell, subagents, a second model), it says how to carry on without it: do that, and name what was missing in the review output. Ask the user only when someone is there to answer.
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ No shell, or no git? Review the diff you were given (a file, a pasted patch, a p
|
|
|
30
30
|
git log --oneline -1 # latest commit
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
2. Determine the base (where this branch diverged from the branch it merges into). The target is the first of these that resolves:
|
|
33
|
+
2. Determine the base (where this branch diverged from the branch it merges into). The target is the first of these that resolves (`gh pr view` asks GitHub, with your own gh login, for the open PR's target branch):
|
|
34
34
|
```bash
|
|
35
35
|
TARGET=$(gh pr view --json baseRefName --jq '"origin/" + .baseRefName' 2>/dev/null) # the open PR's target branch
|
|
36
36
|
[ -n "$TARGET" ] || TARGET=$(git symbolic-ref -q --short refs/remotes/origin/HEAD) # the default branch, set by git clone
|
package/dist/auth.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface TokenResponse {
|
|
|
25
25
|
github_login?: string;
|
|
26
26
|
tenant_name?: string;
|
|
27
27
|
repos?: string[];
|
|
28
|
+
project_memory?: Array<{
|
|
29
|
+
repo: string;
|
|
30
|
+
revision: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
project_memory_status?: "unavailable";
|
|
28
33
|
provisional?: boolean;
|
|
29
34
|
install_url?: string;
|
|
30
35
|
}
|
package/dist/credentials.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export interface Credentials {
|
|
|
21
21
|
github_login: string | null;
|
|
22
22
|
tenant_name: string | null;
|
|
23
23
|
repos: string[];
|
|
24
|
+
/** Device sign-in hint only; the server authorizes every memory operation. */
|
|
25
|
+
project_memory?: Array<{
|
|
26
|
+
repo: string;
|
|
27
|
+
revision: string | null;
|
|
28
|
+
}>;
|
|
29
|
+
project_memory_status?: "unavailable";
|
|
30
|
+
/** Local readiness binding, stable only across refreshes of this sign-in. */
|
|
31
|
+
project_memory_session?: string;
|
|
24
32
|
}
|
|
25
33
|
/** Default credential file path (route-grep-hook.ts checks the same file to know Pharaoh is signed in). */
|
|
26
34
|
export declare const CREDENTIALS_PATH: string;
|
package/dist/gateway.js
CHANGED
|
@@ -105,7 +105,7 @@ export function createGateway(opts) {
|
|
|
105
105
|
*/
|
|
106
106
|
function persistAuthorizedToken(token) {
|
|
107
107
|
const sseUrl = resolveSseUrl(token.sse_url, opts.server);
|
|
108
|
-
const creds = tokenToCredentials(token, sseUrl);
|
|
108
|
+
const creds = tokenToCredentials(token, sseUrl, opts.server);
|
|
109
109
|
writeCredentials(creds, opts.credentialsPath);
|
|
110
110
|
lastCreds = creds;
|
|
111
111
|
pending = null;
|
package/dist/helpers.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare function resolveSseUrl(tokenSseUrl: string | undefined, server: s
|
|
|
38
38
|
* will eventually hit the same silent-expiry cliff we're trying to eliminate.
|
|
39
39
|
* Fail loud so the server regression surfaces instead of being absorbed.
|
|
40
40
|
*/
|
|
41
|
-
export declare function tokenToCredentials(token: TokenResponse, sseUrl: string): Credentials;
|
|
41
|
+
export declare function tokenToCredentials(token: TokenResponse, sseUrl: string, server?: string): Credentials;
|
|
42
42
|
/** Format remaining TTL as human-readable string (e.g. "5d 12h"). */
|
|
43
43
|
export declare function formatTtl(expiresAt: string): string;
|
|
44
44
|
/** Format a credential identity string (e.g. "alice (my-org)"). */
|
package/dist/helpers.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Pure helper functions for the mcp-proxy CLI — no side effects on import.
|
|
3
3
|
* Separated from index.ts so tests can import without triggering main().
|
|
4
4
|
*/
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
5
6
|
import { createInterface } from "node:readline/promises";
|
|
6
7
|
import { isExpired } from "./credentials.js";
|
|
7
8
|
const DEFAULT_SERVER = "https://mcp.pharaoh.so";
|
|
@@ -95,7 +96,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
|
|
|
95
96
|
return { server, logout };
|
|
96
97
|
}
|
|
97
98
|
export function printUsage() {
|
|
98
|
-
printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear credentials, MCP config, the Claude Code plugin and the CLAUDE.md line, then exit", " --clean Remove Pharaoh
|
|
99
|
+
printLines("Usage: pharaoh-mcp [options]", "", "Options:", " --server <url> Pharaoh server URL (default: https://mcp.pharaoh.so)", " --logout Clear credentials, MCP config, the Claude Code plugin and the CLAUDE.md line, then exit", " --clean Remove Pharaoh setup, including managed local project memory hooks", " --install-skills Reinstall the Claude Code plugin and OpenClaw skills", " --version, -v Print the version", " --help, -h Show this help", "", "Get started:", ` ${NPX_COMMAND}`, "");
|
|
99
100
|
}
|
|
100
101
|
/**
|
|
101
102
|
* Validate that a server-supplied SSE URL shares the same origin as the configured server.
|
|
@@ -127,12 +128,20 @@ export function resolveSseUrl(tokenSseUrl, server) {
|
|
|
127
128
|
* will eventually hit the same silent-expiry cliff we're trying to eliminate.
|
|
128
129
|
* Fail loud so the server regression surfaces instead of being absorbed.
|
|
129
130
|
*/
|
|
130
|
-
export function tokenToCredentials(token, sseUrl) {
|
|
131
|
+
export function tokenToCredentials(token, sseUrl, server) {
|
|
131
132
|
if (!token.refresh_token || !token.refresh_token_expires_in) {
|
|
132
133
|
throw new Error("Pharaoh server response missing refresh_token — server is older than this client. " +
|
|
133
134
|
"Update the server to a version that issues refresh tokens, or downgrade the client.");
|
|
134
135
|
}
|
|
135
136
|
const now = Date.now();
|
|
137
|
+
const production = server === DEFAULT_SERVER && new URL(sseUrl).origin === DEFAULT_SERVER;
|
|
138
|
+
const memory = production && Array.isArray(token.project_memory)
|
|
139
|
+
? token.project_memory.filter((entry) => typeof entry?.repo === "string" &&
|
|
140
|
+
/^[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9_.-]{1,100}$/i.test(entry.repo) &&
|
|
141
|
+
(entry.revision === null ||
|
|
142
|
+
(typeof entry.revision === "string" &&
|
|
143
|
+
/^(?!0{40}$)[a-f0-9]{40}$/.test(entry.revision))))
|
|
144
|
+
: [];
|
|
136
145
|
return {
|
|
137
146
|
version: 2,
|
|
138
147
|
access_token: token.access_token,
|
|
@@ -144,6 +153,13 @@ export function tokenToCredentials(token, sseUrl) {
|
|
|
144
153
|
github_login: token.github_login ?? null,
|
|
145
154
|
tenant_name: token.tenant_name ?? null,
|
|
146
155
|
repos: token.repos ?? [],
|
|
156
|
+
...(memory.length ? { project_memory: memory } : {}),
|
|
157
|
+
...(memory.some((entry) => entry.revision === null)
|
|
158
|
+
? { project_memory_session: randomUUID() }
|
|
159
|
+
: {}),
|
|
160
|
+
...(production && token.project_memory_status === "unavailable"
|
|
161
|
+
? { project_memory_status: "unavailable" }
|
|
162
|
+
: {}),
|
|
147
163
|
};
|
|
148
164
|
}
|
|
149
165
|
/** Format remaining TTL as human-readable string (e.g. "5d 12h"). */
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* mode until device flow completes, then swap to SSE in
|
|
13
13
|
* place — the stdio session never disconnects.
|
|
14
14
|
*/
|
|
15
|
+
import { syncMemoryHooks } from "../project-memory/setup.mjs";
|
|
15
16
|
import { pollForToken, printActivationPrompt, printAuthSuccess, requestDeviceCode, } from "./auth.js";
|
|
16
17
|
import { AuthRequiredBackend, createSseBackend } from "./backend.js";
|
|
17
18
|
import { deleteCredentials, readCredentials, writeCredentials, } from "./credentials.js";
|
|
@@ -31,6 +32,16 @@ const BANNER = [
|
|
|
31
32
|
" |_| |_||_/_/ \\_\\_|_\\/_/ \\_\\\\___/|_||_|",
|
|
32
33
|
"",
|
|
33
34
|
].join("\n");
|
|
35
|
+
function reportMemorySetup(options) {
|
|
36
|
+
try {
|
|
37
|
+
const notices = syncMemoryHooks(options);
|
|
38
|
+
if (notices.length)
|
|
39
|
+
printLines(...notices);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
printLines("Pharaoh: project memory setup could not update local activation.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
34
45
|
async function main() {
|
|
35
46
|
const args = process.argv.slice(2);
|
|
36
47
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -57,22 +68,26 @@ async function main() {
|
|
|
57
68
|
// Clean mode — remove all pharaoh traces from Claude Code config (MCP entries,
|
|
58
69
|
// disabled states, project-scoped configs). Useful for resetting broken installs.
|
|
59
70
|
if (args.includes("--clean")) {
|
|
60
|
-
const { cleanSlate } = await import("./setup.js");
|
|
71
|
+
const { cleanCodexSetup, cleanSlate } = await import("./setup.js");
|
|
61
72
|
const { uninstallClaudePlugin } = await import("./install-skills.js");
|
|
62
73
|
cleanSlate();
|
|
63
74
|
uninstallClaudePlugin();
|
|
64
|
-
|
|
75
|
+
cleanCodexSetup();
|
|
76
|
+
reportMemorySetup({ removeLegacy: true });
|
|
77
|
+
printLines("Pharaoh: local setup cleanup finished.");
|
|
65
78
|
process.exit(0);
|
|
66
79
|
}
|
|
67
80
|
const { server, logout } = parseArgs(args);
|
|
68
81
|
if (logout) {
|
|
69
|
-
const { cleanSlate } = await import("./setup.js");
|
|
82
|
+
const { cleanCodexSetup, cleanSlate } = await import("./setup.js");
|
|
70
83
|
const { uninstallClaudePlugin } = await import("./install-skills.js");
|
|
71
84
|
// Credentials first: a cleanup step that fails must never leave the tokens behind.
|
|
72
85
|
deleteCredentials();
|
|
73
86
|
cleanSlate();
|
|
74
87
|
uninstallClaudePlugin();
|
|
75
|
-
|
|
88
|
+
cleanCodexSetup();
|
|
89
|
+
reportMemorySetup({ removeLegacy: true });
|
|
90
|
+
printLines("Pharaoh: credentials cleared and local setup cleanup finished.");
|
|
76
91
|
process.exit(0);
|
|
77
92
|
}
|
|
78
93
|
const isInteractive = Boolean(process.stdin.isTTY);
|
|
@@ -81,10 +96,10 @@ async function main() {
|
|
|
81
96
|
// Running `npx @pharaoh-so/mcp` is the only command a user needs.
|
|
82
97
|
if (isInteractive) {
|
|
83
98
|
process.stderr.write(`${BANNER}\n`);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
99
|
+
const { claudeCliMissing, hasClaude, hasCodex, runCodexSetup, runSetup } = await import("./setup.js");
|
|
100
|
+
const claude = hasClaude();
|
|
101
|
+
const codex = hasCodex();
|
|
102
|
+
if (!claude && !codex) {
|
|
88
103
|
printLines(...claudeCliMissing());
|
|
89
104
|
process.exit(1);
|
|
90
105
|
}
|
|
@@ -97,28 +112,32 @@ async function main() {
|
|
|
97
112
|
printLines(`Pharaoh: provisional access — install the GitHub App to map your repos: ${token.install_url ?? ""}`);
|
|
98
113
|
}
|
|
99
114
|
const sseUrl = resolveSseUrl(token.sse_url, server);
|
|
100
|
-
const newCreds = tokenToCredentials(token, sseUrl);
|
|
115
|
+
const newCreds = tokenToCredentials(token, sseUrl, server);
|
|
101
116
|
writeCredentials(newCreds);
|
|
102
117
|
printAuthSuccess(token.github_login ?? null, token.tenant_name ?? null, token.repos?.length ?? 0);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
const clients = [];
|
|
119
|
+
if (claude && runSetup({ silent: true }))
|
|
120
|
+
clients.push("claude-code");
|
|
121
|
+
// Existing Claude-only installs stay unchanged for nonpilot accounts.
|
|
122
|
+
if (codex && (!claude || newCreds.project_memory?.length)) {
|
|
123
|
+
if (runCodexSetup())
|
|
124
|
+
clients.push("codex");
|
|
125
|
+
}
|
|
126
|
+
if (!clients.length)
|
|
106
127
|
process.exit(1);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (claudeMd)
|
|
121
|
-
printLines(claudeMd);
|
|
128
|
+
if (clients.includes("claude-code")) {
|
|
129
|
+
process.stderr.write(" Setting up...");
|
|
130
|
+
const { keptCommands } = runInstallSkills(undefined, { silent: true });
|
|
131
|
+
process.stderr.write(" done.\n");
|
|
132
|
+
printLines(describeAllowReadTools(allowReadTools()));
|
|
133
|
+
const kept = formatKeptCommands(keptCommands);
|
|
134
|
+
if (kept)
|
|
135
|
+
printLines(kept);
|
|
136
|
+
const claudeMd = describeClaudeMdOutcome(await offerClaudeMdLine(() => askYesNo(CLAUDE_MD_QUESTION), Boolean(process.stdin.isTTY && process.stderr.isTTY)));
|
|
137
|
+
if (claudeMd)
|
|
138
|
+
printLines(claudeMd);
|
|
139
|
+
}
|
|
140
|
+
reportMemorySetup({ credentials: newCreds, clients });
|
|
122
141
|
// The registration is pinned to this version; upgrading is a deliberate
|
|
123
142
|
// re-run. Interactive only, and quiet when the registry can't be reached.
|
|
124
143
|
const latest = await fetchLatestVersion();
|
|
@@ -126,7 +145,7 @@ async function main() {
|
|
|
126
145
|
if (latest && isNewerVersion(latest, running)) {
|
|
127
146
|
printLines(`Pharaoh: ${latest} is available (this is ${running}). Upgrade: ${UPGRADE_COMMAND}`);
|
|
128
147
|
}
|
|
129
|
-
printLines("", "Pharaoh is ready. Try these in a new
|
|
148
|
+
printLines("", "Pharaoh is ready. Try these in a new conversation:", "", ' "What modules does this codebase have?"', ' "Search for functions related to authentication"', ' "What breaks if I change this file?"', "");
|
|
130
149
|
process.exit(0);
|
|
131
150
|
}
|
|
132
151
|
// ── Proxy mode (Claude Code spawned us as a stdio MCP server) ──
|
package/dist/install-skills.d.ts
CHANGED
|
@@ -56,10 +56,11 @@ export declare function installSkills(home?: string): number;
|
|
|
56
56
|
export declare function mergeOpenClawConfig(home?: string): boolean;
|
|
57
57
|
/**
|
|
58
58
|
* Rules the interactive setup adds to `permissions.allow` in
|
|
59
|
-
* ~/.claude/settings.json: the
|
|
59
|
+
* ~/.claude/settings.json: the seven read-only tools (`readOnlyHint: true`),
|
|
60
60
|
* each by full name. Never a prefix rule — `mcp__pharaoh` also auto-approves
|
|
61
|
-
*
|
|
62
|
-
*
|
|
61
|
+
* the tools that change things (`pharaoh_manage_repo`, which queues a re-map,
|
|
62
|
+
* and the superadmin tools), so a prompt injection could run them with
|
|
63
|
+
* no human in the loop (security review SC-6). Those tools keep prompting.
|
|
63
64
|
*/
|
|
64
65
|
export declare const PHARAOH_ALLOW_RULES: readonly string[];
|
|
65
66
|
/** What `allowReadTools` did to ~/.claude/settings.json. */
|
package/dist/install-skills.js
CHANGED
|
@@ -16,8 +16,11 @@ 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/). */
|
|
18
18
|
const PKG_ROOT = join(__dirname, "..");
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Path to the bundled skills: npx's render of skills/ (written by the build's
|
|
21
|
+
* generate:commands), so installs copy them as-is and never see channel markers.
|
|
22
|
+
*/
|
|
23
|
+
const BUNDLED_SKILLS_DIR = join(PKG_ROOT, "npx-skills");
|
|
21
24
|
/** Path to the bundled slash commands directory. */
|
|
22
25
|
const BUNDLED_COMMANDS_DIR = join(PKG_ROOT, "commands");
|
|
23
26
|
/** Path to the bundled Claude Code hooks (hooks.json and the Grep routing hook). */
|
|
@@ -606,10 +609,11 @@ export function mergeOpenClawConfig(home = homedir()) {
|
|
|
606
609
|
// ── Claude Code permissions ─────────────────────────────────────────
|
|
607
610
|
/**
|
|
608
611
|
* Rules the interactive setup adds to `permissions.allow` in
|
|
609
|
-
* ~/.claude/settings.json: the
|
|
612
|
+
* ~/.claude/settings.json: the seven read-only tools (`readOnlyHint: true`),
|
|
610
613
|
* each by full name. Never a prefix rule — `mcp__pharaoh` also auto-approves
|
|
611
|
-
*
|
|
612
|
-
*
|
|
614
|
+
* the tools that change things (`pharaoh_manage_repo`, which queues a re-map,
|
|
615
|
+
* and the superadmin tools), so a prompt injection could run them with
|
|
616
|
+
* no human in the loop (security review SC-6). Those tools keep prompting.
|
|
613
617
|
*/
|
|
614
618
|
export const PHARAOH_ALLOW_RULES = [
|
|
615
619
|
"mcp__pharaoh__get_codebase_map",
|
|
@@ -666,11 +670,11 @@ export function describeAllowReadTools(result) {
|
|
|
666
670
|
const count = PHARAOH_ALLOW_RULES.length;
|
|
667
671
|
switch (result) {
|
|
668
672
|
case "added":
|
|
669
|
-
return `Pharaoh: allowed ${count} tools by name in ~/.claude/settings.json (permissions.allow)
|
|
673
|
+
return `Pharaoh: allowed ${count} read-only tools by name in ~/.claude/settings.json (permissions.allow). pharaoh_manage_repo, which switches or refreshes a repo, still asks first.`;
|
|
670
674
|
case "migrated":
|
|
671
|
-
return `Pharaoh: replaced the blanket mcp__pharaoh rule in ~/.claude/settings.json with ${count} tools by name.
|
|
675
|
+
return `Pharaoh: replaced the blanket mcp__pharaoh rule in ~/.claude/settings.json with ${count} read-only tools by name. pharaoh_manage_repo asks first again.`;
|
|
672
676
|
case "current":
|
|
673
|
-
return `Pharaoh: ~/.claude/settings.json already allows the ${count} read-only
|
|
677
|
+
return `Pharaoh: ~/.claude/settings.json already allows the ${count} read-only tools by name.`;
|
|
674
678
|
case "no-settings":
|
|
675
679
|
return "Pharaoh: no ~/.claude/settings.json yet, so Pharaoh's tools ask before they run. The rules to allow the read-only tools are in the README (Security).";
|
|
676
680
|
case "error":
|
package/dist/refresh.d.ts
CHANGED
|
@@ -48,10 +48,11 @@ export type RefreshFailureAction = "reauth" | "preserve" | "transient";
|
|
|
48
48
|
* @param server Pharaoh server base URL (e.g. "https://mcp.pharaoh.so")
|
|
49
49
|
* @param path optional custom credentials path (for tests)
|
|
50
50
|
* @param timeoutMs how long the refresh request may take (for tests)
|
|
51
|
+
* @param strictOrigin require credentials from this origin and refuse refresh redirects
|
|
51
52
|
* @returns New or adopted credentials, or a classified failure. A lock held by
|
|
52
53
|
* another proxy for too long is `transient` (credentials untouched).
|
|
53
54
|
*/
|
|
54
|
-
export declare function refreshIfNeeded(creds: Credentials, server: string, path?: string, timeoutMs?: number): Promise<RefreshOutcome>;
|
|
55
|
+
export declare function refreshIfNeeded(creds: Credentials, server: string, path?: string, timeoutMs?: number, strictOrigin?: boolean): Promise<RefreshOutcome>;
|
|
55
56
|
/**
|
|
56
57
|
* Read credentials from disk and refresh if the access token is expired.
|
|
57
58
|
*
|
|
@@ -63,4 +64,9 @@ export declare function refreshIfNeeded(creds: Credentials, server: string, path
|
|
|
63
64
|
* Intended use: called at proxy startup to replace the current "die if expired"
|
|
64
65
|
* branch in index.ts.
|
|
65
66
|
*/
|
|
66
|
-
export declare function loadAndRefresh(server: string, path?: string): Promise<RefreshOutcome | null>;
|
|
67
|
+
export declare function loadAndRefresh(server: string, path?: string, strictOrigin?: boolean): Promise<RefreshOutcome | null>;
|
|
68
|
+
/** Refresh for a client that accepts credentials only from its configured origin. */
|
|
69
|
+
export declare function originBoundRefresh(server: string, path?: string): {
|
|
70
|
+
load: () => Promise<RefreshOutcome | null>;
|
|
71
|
+
renew: (creds: Credentials) => Promise<RefreshOutcome>;
|
|
72
|
+
};
|